From a42addfe084748faa07271b57c993e695d590118 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Mon, 6 May 2024 13:27:50 +0200 Subject: [PATCH 1/4] `Parameter`: use a specialized method for short name resolution so far, only `TableStruct` parameters could specify references to other parameters via short name reference. Since there could be other parameters which could require this, let's make this more generic (i.e., available for all parameters). Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- odxtools/basicstructure.py | 6 +----- odxtools/parameters/codedconstparameter.py | 5 +++-- odxtools/parameters/lengthkeyparameter.py | 7 ++++--- odxtools/parameters/nrcconstparameter.py | 5 +++-- odxtools/parameters/parameter.py | 6 ++++++ odxtools/parameters/parameterwithdop.py | 5 +++-- odxtools/parameters/physicalconstantparameter.py | 7 ++++--- odxtools/parameters/tablekeyparameter.py | 5 +++-- odxtools/parameters/tablestructparameter.py | 13 ++++--------- odxtools/parameters/valueparameter.py | 7 ++++--- tests/test_decoding.py | 13 +++++++------ tests/test_encoding.py | 6 +++--- 12 files changed, 45 insertions(+), 40 deletions(-) diff --git a/odxtools/basicstructure.py b/odxtools/basicstructure.py index 335ddbb7..b3bc7d7e 100644 --- a/odxtools/basicstructure.py +++ b/odxtools/basicstructure.py @@ -23,7 +23,6 @@ from .parameters.parameterwithdop import ParameterWithDOP from .parameters.physicalconstantparameter import PhysicalConstantParameter from .parameters.tablekeyparameter import TableKeyParameter -from .parameters.tablestructparameter import TableStructParameter from .utils import dataclass_fields_asdict if TYPE_CHECKING: @@ -307,7 +306,4 @@ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: super()._resolve_snrefs(diag_layer) for param in self.parameters: - if isinstance(param, TableStructParameter): - param._table_struct_resolve_snrefs(diag_layer, param_list=self.parameters) - else: - param._resolve_snrefs(diag_layer) + param._parameter_resolve_snrefs(diag_layer, param_list=self.parameters) diff --git a/odxtools/parameters/codedconstparameter.py b/odxtools/parameters/codedconstparameter.py index e2ae4c84..e6f2c5d3 100644 --- a/odxtools/parameters/codedconstparameter.py +++ b/odxtools/parameters/codedconstparameter.py @@ -59,8 +59,9 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) @override - def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: - super()._resolve_snrefs(diag_layer) + def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, + param_list: List[Parameter]) -> None: + super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) @override def get_static_bit_length(self) -> Optional[int]: diff --git a/odxtools/parameters/lengthkeyparameter.py b/odxtools/parameters/lengthkeyparameter.py index 9410f6a9..6a705be6 100644 --- a/odxtools/parameters/lengthkeyparameter.py +++ b/odxtools/parameters/lengthkeyparameter.py @@ -11,7 +11,7 @@ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from ..odxtypes import ParameterValue from ..utils import dataclass_fields_asdict -from .parameter import ParameterType +from .parameter import Parameter, ParameterType from .parameterwithdop import ParameterWithDOP if TYPE_CHECKING: @@ -60,8 +60,9 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) @override - def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: - super()._resolve_snrefs(diag_layer) + def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, + param_list: List[Parameter]) -> None: + super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) @property @override diff --git a/odxtools/parameters/nrcconstparameter.py b/odxtools/parameters/nrcconstparameter.py index 3419dbd3..6cbd1a5b 100644 --- a/odxtools/parameters/nrcconstparameter.py +++ b/odxtools/parameters/nrcconstparameter.py @@ -75,8 +75,9 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) @override - def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: - super()._resolve_snrefs(diag_layer) + def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, + param_list: List[Parameter]) -> None: + super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) @override def get_static_bit_length(self) -> Optional[int]: diff --git a/odxtools/parameters/parameter.py b/odxtools/parameters/parameter.py index 7705802c..7ade96ed 100644 --- a/odxtools/parameters/parameter.py +++ b/odxtools/parameters/parameter.py @@ -82,7 +82,13 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: for sdg in self.sdgs: sdg._resolve_odxlinks(odxlinks) + @final def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: + raise RuntimeError("Calling _resolve_snrefs() is not allowed for parameters. " + "Use _parameter_resolve_snrefs() instead.") + + def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, + param_list: List["Parameter"]) -> None: for sdg in self.sdgs: sdg._resolve_snrefs(diag_layer) diff --git a/odxtools/parameters/parameterwithdop.py b/odxtools/parameters/parameterwithdop.py index 06aec3f6..26848071 100644 --- a/odxtools/parameters/parameterwithdop.py +++ b/odxtools/parameters/parameterwithdop.py @@ -61,8 +61,9 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: self._dop = odxlinks.resolve_lenient(self.dop_ref) @override - def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: - super()._resolve_snrefs(diag_layer) + def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, + param_list: List[Parameter]) -> None: + super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) if self.dop_snref: ddds = diag_layer.diag_data_dictionary_spec diff --git a/odxtools/parameters/physicalconstantparameter.py b/odxtools/parameters/physicalconstantparameter.py index df58c8bc..f34dbf65 100644 --- a/odxtools/parameters/physicalconstantparameter.py +++ b/odxtools/parameters/physicalconstantparameter.py @@ -12,7 +12,7 @@ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from ..odxtypes import ParameterValue from ..utils import dataclass_fields_asdict -from .parameter import ParameterType +from .parameter import Parameter, ParameterType from .parameterwithdop import ParameterWithDOP if TYPE_CHECKING: @@ -50,8 +50,9 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) @override - def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: - super()._resolve_snrefs(diag_layer) + def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, + param_list: List[Parameter]) -> None: + super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) dop = odxrequire(self.dop) if not isinstance(dop, DataObjectProperty): diff --git a/odxtools/parameters/tablekeyparameter.py b/odxtools/parameters/tablekeyparameter.py index 564bdc33..e06a40b9 100644 --- a/odxtools/parameters/tablekeyparameter.py +++ b/odxtools/parameters/tablekeyparameter.py @@ -94,8 +94,9 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: self._table = self._table_row.table @override - def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: - super()._resolve_snrefs(diag_layer) + def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, + param_list: List[Parameter]) -> None: + super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) if self.table_snref is not None: ddd_spec = diag_layer.diag_data_dictionary_spec diff --git a/odxtools/parameters/tablestructparameter.py b/odxtools/parameters/tablestructparameter.py index adfe0d55..66119d5c 100644 --- a/odxtools/parameters/tablestructparameter.py +++ b/odxtools/parameters/tablestructparameter.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast from xml.etree import ElementTree -from typing_extensions import final, override +from typing_extensions import override from ..decodestate import DecodeState from ..encodestate import EncodeState @@ -60,14 +60,9 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: self._table_key = odxlinks.resolve(self.table_key_ref, TableKeyParameter) @override - @final - def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: - raise RuntimeError(f"Calling TableStructParameter._resolve_snref() is not allowed. " - f"Use ._table_struct_resolve_snrefs() instead.") - - def _table_struct_resolve_snrefs(self, diag_layer: "DiagLayer", *, - param_list: List[Parameter]) -> None: - super()._resolve_snrefs(diag_layer) + def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, + param_list: List[Parameter]) -> None: + super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) if self.table_key_snref is not None: tk_candidates = [p for p in param_list if p.short_name == self.table_key_snref] diff --git a/odxtools/parameters/valueparameter.py b/odxtools/parameters/valueparameter.py index b8737d91..1ef22d60 100644 --- a/odxtools/parameters/valueparameter.py +++ b/odxtools/parameters/valueparameter.py @@ -11,7 +11,7 @@ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from ..odxtypes import AtomicOdxType, ParameterValue from ..utils import dataclass_fields_asdict -from .parameter import ParameterType +from .parameter import Parameter, ParameterType from .parameterwithdop import ParameterWithDOP if TYPE_CHECKING: @@ -50,8 +50,9 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: super()._resolve_odxlinks(odxlinks) @override - def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: - super()._resolve_snrefs(diag_layer) + def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, + param_list: List[Parameter]) -> None: + super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) if self.physical_default_value_raw is not None: dop = odxrequire(self.dop) diff --git a/tests/test_decoding.py b/tests/test_decoding.py index 9a8db7b0..770e166c 100644 --- a/tests/test_decoding.py +++ b/tests/test_decoding.py @@ -1870,8 +1870,9 @@ def setUp(self) -> None: self.parameter_termination_end_of_pdu._resolve_odxlinks(odxlinks) self.parameter_sid._resolve_odxlinks(odxlinks) - self.parameter_termination_end_of_pdu._resolve_snrefs(None) # type: ignore[arg-type] - self.parameter_sid._resolve_snrefs(None) # type: ignore[arg-type] + self.parameter_termination_end_of_pdu._parameter_resolve_snrefs( + cast(DiagLayer, None), param_list=[]) + self.parameter_sid._parameter_resolve_snrefs(cast(DiagLayer, None), param_list=[]) def test_min_max_length_type_end_of_pdu(self) -> None: req_param1 = self.parameter_sid @@ -1939,8 +1940,8 @@ def test_min_max_length_type_end_of_pdu_in_structure(self) -> None: req_param1._resolve_odxlinks(odxlinks) req_param2._resolve_odxlinks(odxlinks) - req_param1._resolve_snrefs(cast(DiagLayer, None)) - req_param2._resolve_snrefs(cast(DiagLayer, None)) + req_param1._parameter_resolve_snrefs(cast(DiagLayer, None), param_list=[]) + req_param2._parameter_resolve_snrefs(cast(DiagLayer, None), param_list=[]) expected_coded_message = bytes([0x12, 0x34]) expected_param_dict = { @@ -2033,8 +2034,8 @@ def test_physical_constant_parameter(self) -> None: req_param1._resolve_odxlinks(odxlinks) req_param2._resolve_odxlinks(odxlinks) - req_param1._resolve_snrefs(cast(DiagLayer, None)) - req_param2._resolve_snrefs(cast(DiagLayer, None)) + req_param1._parameter_resolve_snrefs(cast(DiagLayer, None), param_list=[]) + req_param2._parameter_resolve_snrefs(cast(DiagLayer, None), param_list=[]) expected_coded_message = bytes([0x12, 0x0]) expected_param_dict = {"SID": 0x12, "physical_constant_parameter": offset} diff --git a/tests/test_encoding.py b/tests/test_encoding.py index 6a4c217e..a882342a 100644 --- a/tests/test_encoding.py +++ b/tests/test_encoding.py @@ -170,7 +170,7 @@ def test_encode_linear(self) -> None: ) param1._resolve_odxlinks(odxlinks) - param1._resolve_snrefs(cast(DiagLayer, None)) + param1._parameter_resolve_snrefs(cast(DiagLayer, None), param_list=req.parameters) # Missing mandatory parameter. with self.assertRaises(EncodeError): @@ -402,7 +402,7 @@ def test_bit_mask(self) -> None: sdgs=[], physical_default_value_raw=None) inner_param._resolve_odxlinks(odxlinks) - inner_param._resolve_snrefs(cast(DiagLayer, None)) + inner_param._parameter_resolve_snrefs(cast(DiagLayer, None), param_list=[]) # Outer outer_param = ValueParameter( @@ -417,7 +417,7 @@ def test_bit_mask(self) -> None: sdgs=[], physical_default_value_raw=None) outer_param._resolve_odxlinks(odxlinks) - outer_param._resolve_snrefs(cast(DiagLayer, None)) + outer_param._parameter_resolve_snrefs(cast(DiagLayer, None), param_list=[]) req = self._create_request([inner_param, outer_param]) From 3390f8438d7d77468b0a6a252fa05aacd0edd660 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Mon, 6 May 2024 13:28:20 +0200 Subject: [PATCH 2/4] improve the resolution of short name references using a dedicated function for short name resolution is IMO conceptually cleaner than relying on `NamedItemList.get()` and -- more importantly -- it works for short names that are not valid python identifiers, i.e., ones that start with a digit. (`NamedItemList` prefixes those with an underscore which causes the short name resolution to fail for such objects.) Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- odxtools/diagcomm.py | 14 ++++++-- odxtools/diaglayerraw.py | 10 +++--- odxtools/field.py | 7 ++-- odxtools/matchingparameter.py | 4 +-- odxtools/multiplexercase.py | 4 +-- odxtools/multiplexerdefaultcase.py | 10 ++++-- odxtools/parameters/parameterwithdop.py | 6 ++-- odxtools/parameters/tablekeyparameter.py | 13 ++++---- odxtools/parameters/tablestructparameter.py | 18 ++-------- odxtools/statechart.py | 14 +++----- odxtools/statetransition.py | 11 ++---- odxtools/tablerow.py | 8 +++-- odxtools/utils.py | 37 ++++++++++++++++++++- 13 files changed, 91 insertions(+), 65 deletions(-) diff --git a/odxtools/diagcomm.py b/odxtools/diagcomm.py index d6e92515..73e0b692 100644 --- a/odxtools/diagcomm.py +++ b/odxtools/diagcomm.py @@ -16,7 +16,7 @@ from .specialdatagroup import SpecialDataGroup from .state import State from .statetransition import StateTransition -from .utils import dataclass_fields_asdict +from .utils import dataclass_fields_asdict, resolve_snref if TYPE_CHECKING: from .diaglayer import DiagLayer @@ -211,5 +211,13 @@ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: for sdg in self.sdgs: sdg._resolve_snrefs(diag_layer) - self._protocols = NamedItemList( - [diag_layer.protocols[prot_snref] for prot_snref in self.protocol_snrefs]) + if TYPE_CHECKING: + self._protocols = NamedItemList([ + resolve_snref(prot_snref, diag_layer.protocols, DiagLayer) + for prot_snref in self.protocol_snrefs + ]) + else: + self._protocols = NamedItemList([ + resolve_snref(prot_snref, diag_layer.protocols) + for prot_snref in self.protocol_snrefs + ]) diff --git a/odxtools/diaglayerraw.py b/odxtools/diaglayerraw.py index 42d931e4..9392884e 100644 --- a/odxtools/diaglayerraw.py +++ b/odxtools/diaglayerraw.py @@ -27,7 +27,7 @@ from .singleecujob import SingleEcuJob from .specialdatagroup import SpecialDataGroup from .statechart import StateChart -from .utils import dataclass_fields_asdict +from .utils import dataclass_fields_asdict, resolve_snref if TYPE_CHECKING: from .diaglayer import DiagLayer @@ -281,8 +281,8 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: self._prot_stack: Optional[ProtStack] = None if self.prot_stack_snref is not None: - self._prot_stack = odxrequire( - odxrequire(self.comparam_spec).prot_stacks.get(self.prot_stack_snref)) + self._prot_stack = resolve_snref(self.prot_stack_snref, + odxrequire(self.comparam_spec).prot_stacks, ProtStack) # do short-name reference resolution if self.admin_data is not None: @@ -292,8 +292,8 @@ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: for company_data in self.company_datas: company_data._resolve_snrefs(diag_layer) - for functional_classe in self.functional_classes: - functional_classe._resolve_snrefs(diag_layer) + for functional_class in self.functional_classes: + functional_class._resolve_snrefs(diag_layer) for diag_comm in self.diag_comms: if isinstance(diag_comm, OdxLinkRef): continue diff --git a/odxtools/field.py b/odxtools/field.py index 50589892..17e680db 100644 --- a/odxtools/field.py +++ b/odxtools/field.py @@ -8,7 +8,7 @@ from .exceptions import odxassert, odxrequire from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef from .odxtypes import odxstr_to_bool -from .utils import dataclass_fields_asdict +from .utils import dataclass_fields_asdict, resolve_snref if TYPE_CHECKING: from .diaglayer import DiagLayer @@ -84,8 +84,9 @@ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: """Recursively resolve any short-name references""" if self.structure_snref is not None: structures = diag_layer.diag_data_dictionary_spec.structures - self._structure = odxrequire(structures.get(self.structure_snref)) + self._structure = resolve_snref(self.structure_snref, structures, BasicStructure) if self.env_data_desc_snref is not None: env_data_descs = diag_layer.diag_data_dictionary_spec.env_data_descs - self._env_data_desc = odxrequire(env_data_descs.get(self.env_data_desc_snref)) + self._env_data_desc = resolve_snref(self.env_data_desc_snref, env_data_descs, + EnvironmentDataDescription) diff --git a/odxtools/matchingparameter.py b/odxtools/matchingparameter.py index bb9288d9..25ff1add 100644 --- a/odxtools/matchingparameter.py +++ b/odxtools/matchingparameter.py @@ -32,8 +32,8 @@ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "MatchingParameter": expected_value = odxrequire(et_element.findtext("EXPECTED-VALUE")) - diag_com_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF")) - diag_comm_snref = odxrequire(diag_com_snref_el.get("SHORT-NAME")) + diag_comm_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF")) + diag_comm_snref = odxrequire(diag_comm_snref_el.get("SHORT-NAME")) out_param_snref_el = et_element.find("OUT-PARAM-IF-SNREF") out_param_snpathref_el = et_element.find("OUT-PARAM-IF-SNPATHREF") out_param_if = None diff --git a/odxtools/multiplexercase.py b/odxtools/multiplexercase.py index d7b16c53..a65fee3c 100644 --- a/odxtools/multiplexercase.py +++ b/odxtools/multiplexercase.py @@ -9,7 +9,7 @@ from .exceptions import odxrequire from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef from .odxtypes import AtomicOdxType, DataType -from .utils import dataclass_fields_asdict +from .utils import dataclass_fields_asdict, resolve_snref if TYPE_CHECKING: from .diaglayer import DiagLayer @@ -73,7 +73,7 @@ def _mux_case_resolve_odxlinks(self, odxlinks: OdxLinkDatabase, *, def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: if self.structure_snref: ddds = diag_layer.diag_data_dictionary_spec - self._structure = odxrequire(ddds.structures.get(self.structure_snref)) + self._structure = resolve_snref(self.structure_snref, ddds.structures, BasicStructure) def applies(self, value: AtomicOdxType) -> bool: return self.lower_limit.complies_to_lower(value) \ diff --git a/odxtools/multiplexerdefaultcase.py b/odxtools/multiplexerdefaultcase.py index 9b77e732..18c896cb 100644 --- a/odxtools/multiplexerdefaultcase.py +++ b/odxtools/multiplexerdefaultcase.py @@ -7,7 +7,7 @@ from .element import NamedElement from .exceptions import odxrequire from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef -from .utils import dataclass_fields_asdict +from .utils import dataclass_fields_asdict, resolve_snref if TYPE_CHECKING: from .diaglayer import DiagLayer @@ -20,7 +20,7 @@ class MultiplexerDefaultCase(NamedElement): structure_snref: Optional[str] def __post_init__(self) -> None: - self._structure: Optional[BasicStructure] = None + self._structure: BasicStructure @staticmethod def from_et(et_element: ElementTree.Element, @@ -46,4 +46,8 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: if self.structure_snref: ddds = diag_layer.diag_data_dictionary_spec - self._structure = odxrequire(ddds.structures.get(self.structure_snref)) + self._structure = resolve_snref(self.structure_snref, ddds.structures, BasicStructure) + + @property + def structure(self) -> BasicStructure: + return self._structure diff --git a/odxtools/parameters/parameterwithdop.py b/odxtools/parameters/parameterwithdop.py index 26848071..00ee8622 100644 --- a/odxtools/parameters/parameterwithdop.py +++ b/odxtools/parameters/parameterwithdop.py @@ -14,7 +14,7 @@ from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef from ..odxtypes import AtomicOdxType, ParameterValue from ..physicaltype import PhysicalType -from ..utils import dataclass_fields_asdict +from ..utils import dataclass_fields_asdict, resolve_snref from .parameter import Parameter if TYPE_CHECKING: @@ -66,8 +66,8 @@ def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) if self.dop_snref: - ddds = diag_layer.diag_data_dictionary_spec - self._dop = odxrequire(ddds.all_data_object_properties.get(self.dop_snref)) + all_dops = diag_layer.diag_data_dictionary_spec.all_data_object_properties + self._dop = resolve_snref(self.dop_snref, all_dops, DopBase) @property def dop(self) -> DopBase: diff --git a/odxtools/parameters/tablekeyparameter.py b/odxtools/parameters/tablekeyparameter.py index e06a40b9..66cce677 100644 --- a/odxtools/parameters/tablekeyparameter.py +++ b/odxtools/parameters/tablekeyparameter.py @@ -10,7 +10,7 @@ from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef from ..odxtypes import ParameterValue -from ..utils import dataclass_fields_asdict +from ..utils import dataclass_fields_asdict, resolve_snref from .parameter import Parameter, ParameterType if TYPE_CHECKING: @@ -99,15 +99,14 @@ def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) if self.table_snref is not None: - ddd_spec = diag_layer.diag_data_dictionary_spec - self._table = ddd_spec.tables[self.table_snref] + tables = diag_layer.diag_data_dictionary_spec.tables + self._table = resolve_snref(self.table_snref, tables, Table) if self.table_row_snref is not None: # make sure that we know the table to which the table row # SNREF is relative to. - table = odxrequire( - self._table, "If a table-row short name reference is defined, a " - "table must also be specified.") - self._table_row = table.table_rows[self.table_row_snref] + table = odxrequire(self._table, + "If a table-row is referenced, a table must also be referenced.") + self._table_row = resolve_snref(self.table_row_snref, table.table_rows, TableRow) @property def table(self) -> "Table": diff --git a/odxtools/parameters/tablestructparameter.py b/odxtools/parameters/tablestructparameter.py index 66119d5c..48fb64f2 100644 --- a/odxtools/parameters/tablestructparameter.py +++ b/odxtools/parameters/tablestructparameter.py @@ -10,7 +10,7 @@ from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef from ..odxtypes import ParameterValue -from ..utils import dataclass_fields_asdict +from ..utils import dataclass_fields_asdict, resolve_snref from .parameter import Parameter, ParameterType from .tablekeyparameter import TableKeyParameter @@ -65,21 +65,7 @@ def _parameter_resolve_snrefs(self, diag_layer: "DiagLayer", *, super()._parameter_resolve_snrefs(diag_layer, param_list=param_list) if self.table_key_snref is not None: - tk_candidates = [p for p in param_list if p.short_name == self.table_key_snref] - if len(tk_candidates) > 1: - odxraise(f"Short name reference '{self.table_key_snref}' could " - f"not be uniquely resolved.") - elif len(tk_candidates) == 0: - odxraise(f"Short name reference '{self.table_key_snref}' could " - f"not be resolved.") - return - - tk = tk_candidates[0] - if not isinstance(tk, TableKeyParameter): - odxraise(f"Table struct '{self.short_name}' references non-TableKey parameter " - f"`{self.table_key_snref}' as its table key.") - - self._table_key = tk + self._table_key = resolve_snref(self.table_key_snref, param_list, TableKeyParameter) @property def table_key(self) -> TableKeyParameter: diff --git a/odxtools/statechart.py b/odxtools/statechart.py index 24bdf42e..83fcab58 100644 --- a/odxtools/statechart.py +++ b/odxtools/statechart.py @@ -9,7 +9,7 @@ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from .state import State from .statetransition import StateTransition -from .utils import dataclass_fields_asdict +from .utils import dataclass_fields_asdict, resolve_snref if TYPE_CHECKING: from .diaglayer import DiagLayer @@ -68,19 +68,15 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: for st in self.states: st._resolve_odxlinks(odxlinks) - # For now, we assume that the start state short name ref - # points to a state local to the state chart. TODO: The XML + def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: + # For now, we assume that the start state short name reference + # points to a local state of the state chart. TODO: The XSD # allows to define state charts without any states, yet the # start state SNREF is mandatory. Is this a gap in the spec or # does it allow "foreign" start states? If the latter, what # does that mean? - self._start_state: State - for st in self.states: - if st.short_name == self.start_state_snref: - self._start_state = st - break + self._start_state = resolve_snref(self.start_state_snref, self.states, State) - def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: for st in self.states: st._resolve_snrefs(diag_layer) diff --git a/odxtools/statetransition.py b/odxtools/statetransition.py index 5b40e131..54a68a1b 100644 --- a/odxtools/statetransition.py +++ b/odxtools/statetransition.py @@ -7,7 +7,7 @@ from .exceptions import odxrequire from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from .state import State -from .utils import dataclass_fields_asdict +from .utils import dataclass_fields_asdict, resolve_snref if TYPE_CHECKING: from .diaglayer import DiagLayer @@ -55,10 +55,5 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: # chart. To mitigate this a bit, the non-standard parameters are # keyword-only... def _resolve_snrefs(self, diag_layer: "DiagLayer", *, states: Iterable[State]) -> None: - self._source_state: State - self._target_state: State - for st in states: - if st.short_name == self.source_snref: - self._source_state = st - if st.short_name == self.target_snref: - self._target_state = st + self._source_state = resolve_snref(self.source_snref, states, State) + self._target_state = resolve_snref(self.target_snref, states, State) diff --git a/odxtools/tablerow.py b/odxtools/tablerow.py index 776f0e2c..6ba9d153 100644 --- a/odxtools/tablerow.py +++ b/odxtools/tablerow.py @@ -12,7 +12,7 @@ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef from .odxtypes import AtomicOdxType from .specialdatagroup import SpecialDataGroup -from .utils import dataclass_fields_asdict +from .utils import dataclass_fields_asdict, resolve_snref if TYPE_CHECKING: from .diaglayer import DiagLayer @@ -128,9 +128,11 @@ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: ddd_spec = diag_layer.diag_data_dictionary_spec if self.structure_snref is not None: - self._structure = odxrequire(ddd_spec.structures.get(self.structure_snref)) + self._structure = resolve_snref(self.structure_snref, ddd_spec.structures, + BasicStructure) if self.dop_snref is not None: - self._dop = odxrequire(ddd_spec.data_object_props.get(self.dop_snref)) + self._dop = resolve_snref(self.dop_snref, ddd_spec.data_object_props, + DataObjectProperty) for sdg in self.sdgs: sdg._resolve_snrefs(diag_layer) diff --git a/odxtools/utils.py b/odxtools/utils.py index d8e7aac8..cdd5703a 100644 --- a/odxtools/utils.py +++ b/odxtools/utils.py @@ -1,9 +1,14 @@ # SPDX-License-Identifier: MIT import dataclasses import re -from typing import Any, Dict, Optional +from typing import Any, Dict, Iterable, Optional, Type, TypeVar, overload from xml.etree import ElementTree +from .exceptions import odxraise +from .nameditemlist import OdxNamed + +T = TypeVar("T") + def create_description_from_et(et_element: Optional[ElementTree.Element],) -> Optional[str]: """Read a description tag. @@ -60,3 +65,33 @@ def is_short_name_path(test_val: str) -> bool: See also: ISO 22901 section 7.3.13.3 """ return _short_name_path_pattern.fullmatch(test_val) is not None + + +@overload +def resolve_snref(target_short_name: str, + items: Iterable[OdxNamed], + expected_type: None = None) -> Any: + """Properly resolve a short name reference""" + ... + + +@overload +def resolve_snref(target_short_name: str, items: Iterable[OdxNamed], expected_type: Type[T]) -> T: + ... + + +def resolve_snref(target_short_name: str, + items: Iterable[OdxNamed], + expected_type: Any = None) -> Any: + candidates = [x for x in items if x.short_name == target_short_name] + + if not candidates: + odxraise(f"Cannot resolve short name reference to '{target_short_name}'") + return None + elif len(candidates) > 1: + odxraise(f"Cannot uniquely resolve short name reference to '{target_short_name}'") + elif expected_type is not None and not isinstance(candidates[0], expected_type): + odxraise(f"Reference '{target_short_name}' points to a {type(candidates[0]).__name__}" + f"object while expecting {expected_type.__name__}") + + return candidates[0] From 7b8e7339ec4a192297d8022048da53a749dab16f Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Mon, 6 May 2024 18:49:21 +0200 Subject: [PATCH 3/4] `resolve_snref()`: unstead of a generic type, use `TNamed` from nameditemlist as return type thanks to [at]kayoub5 for the proposal. Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- odxtools/utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/odxtools/utils.py b/odxtools/utils.py index cdd5703a..1ac9f100 100644 --- a/odxtools/utils.py +++ b/odxtools/utils.py @@ -1,13 +1,11 @@ # SPDX-License-Identifier: MIT import dataclasses import re -from typing import Any, Dict, Iterable, Optional, Type, TypeVar, overload +from typing import Any, Dict, Iterable, Optional, Type, overload from xml.etree import ElementTree from .exceptions import odxraise -from .nameditemlist import OdxNamed - -T = TypeVar("T") +from .nameditemlist import OdxNamed, TNamed def create_description_from_et(et_element: Optional[ElementTree.Element],) -> Optional[str]: @@ -76,7 +74,8 @@ def resolve_snref(target_short_name: str, @overload -def resolve_snref(target_short_name: str, items: Iterable[OdxNamed], expected_type: Type[T]) -> T: +def resolve_snref(target_short_name: str, items: Iterable[OdxNamed], + expected_type: Type[TNamed]) -> TNamed: ... From ac56647d3aaa7bb014269ad7ef1c285055e35495 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Mon, 6 May 2024 19:48:25 +0200 Subject: [PATCH 4/4] move `resolve_snref()` from `utils.py` to `odxlink.py` As usual, this was suggested by [at]kayoub5. Thanks! Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- odxtools/diagcomm.py | 4 +-- odxtools/diaglayerraw.py | 4 +-- odxtools/field.py | 4 +-- odxtools/multiplexercase.py | 4 +-- odxtools/multiplexerdefaultcase.py | 4 +-- odxtools/odxlink.py | 36 +++++++++++++++++++-- odxtools/parameters/parameterwithdop.py | 4 +-- odxtools/parameters/tablekeyparameter.py | 4 +-- odxtools/parameters/tablestructparameter.py | 4 +-- odxtools/statechart.py | 4 +-- odxtools/statetransition.py | 4 +-- odxtools/tablerow.py | 4 +-- odxtools/utils.py | 36 +-------------------- 13 files changed, 57 insertions(+), 59 deletions(-) diff --git a/odxtools/diagcomm.py b/odxtools/diagcomm.py index 73e0b692..b190b400 100644 --- a/odxtools/diagcomm.py +++ b/odxtools/diagcomm.py @@ -11,12 +11,12 @@ from .exceptions import odxraise, odxrequire from .functionalclass import FunctionalClass from .nameditemlist import NamedItemList -from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef +from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref from .odxtypes import odxstr_to_bool from .specialdatagroup import SpecialDataGroup from .state import State from .statetransition import StateTransition -from .utils import dataclass_fields_asdict, resolve_snref +from .utils import dataclass_fields_asdict if TYPE_CHECKING: from .diaglayer import DiagLayer diff --git a/odxtools/diaglayerraw.py b/odxtools/diaglayerraw.py index 9392884e..cdc5dca7 100644 --- a/odxtools/diaglayerraw.py +++ b/odxtools/diaglayerraw.py @@ -19,7 +19,7 @@ from .exceptions import odxassert, odxraise, odxrequire from .functionalclass import FunctionalClass from .nameditemlist import NamedItemList -from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef +from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref from .parentref import ParentRef from .protstack import ProtStack from .request import Request @@ -27,7 +27,7 @@ from .singleecujob import SingleEcuJob from .specialdatagroup import SpecialDataGroup from .statechart import StateChart -from .utils import dataclass_fields_asdict, resolve_snref +from .utils import dataclass_fields_asdict if TYPE_CHECKING: from .diaglayer import DiagLayer diff --git a/odxtools/field.py b/odxtools/field.py index 17e680db..89ac409e 100644 --- a/odxtools/field.py +++ b/odxtools/field.py @@ -6,9 +6,9 @@ from .complexdop import ComplexDop from .environmentdatadescription import EnvironmentDataDescription from .exceptions import odxassert, odxrequire -from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef +from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef, resolve_snref from .odxtypes import odxstr_to_bool -from .utils import dataclass_fields_asdict, resolve_snref +from .utils import dataclass_fields_asdict if TYPE_CHECKING: from .diaglayer import DiagLayer diff --git a/odxtools/multiplexercase.py b/odxtools/multiplexercase.py index a65fee3c..2e8d0eeb 100644 --- a/odxtools/multiplexercase.py +++ b/odxtools/multiplexercase.py @@ -7,9 +7,9 @@ from .compumethods.limit import Limit from .element import NamedElement from .exceptions import odxrequire -from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef +from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref from .odxtypes import AtomicOdxType, DataType -from .utils import dataclass_fields_asdict, resolve_snref +from .utils import dataclass_fields_asdict if TYPE_CHECKING: from .diaglayer import DiagLayer diff --git a/odxtools/multiplexerdefaultcase.py b/odxtools/multiplexerdefaultcase.py index 18c896cb..45f289ce 100644 --- a/odxtools/multiplexerdefaultcase.py +++ b/odxtools/multiplexerdefaultcase.py @@ -6,8 +6,8 @@ from .basicstructure import BasicStructure from .element import NamedElement from .exceptions import odxrequire -from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef -from .utils import dataclass_fields_asdict, resolve_snref +from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref +from .utils import dataclass_fields_asdict if TYPE_CHECKING: from .diaglayer import DiagLayer diff --git a/odxtools/odxlink.py b/odxtools/odxlink.py index 76a9a239..b6ba55cf 100644 --- a/odxtools/odxlink.py +++ b/odxtools/odxlink.py @@ -1,10 +1,11 @@ # SPDX-License-Identifier: MIT import warnings from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Type, TypeVar, overload +from typing import Any, Dict, Iterable, List, Optional, Type, TypeVar, overload from xml.etree import ElementTree -from .exceptions import OdxWarning, odxassert +from .exceptions import OdxWarning, odxassert, odxraise +from .nameditemlist import OdxNamed, TNamed @dataclass(frozen=True) @@ -270,3 +271,34 @@ def update(self, new_entries: Dict[OdxLinkId, Any]) -> None: self._db[doc_frag] = {} self._db[doc_frag][odx_id] = obj + + +@overload +def resolve_snref(target_short_name: str, + items: Iterable[OdxNamed], + expected_type: None = None) -> Any: + """Resolve a short name reference given a sequence of candidate objects""" + ... + + +@overload +def resolve_snref(target_short_name: str, items: Iterable[OdxNamed], + expected_type: Type[TNamed]) -> TNamed: + ... + + +def resolve_snref(target_short_name: str, + items: Iterable[OdxNamed], + expected_type: Any = None) -> Any: + candidates = [x for x in items if x.short_name == target_short_name] + + if not candidates: + odxraise(f"Cannot resolve short name reference to '{target_short_name}'") + return None + elif len(candidates) > 1: + odxraise(f"Cannot uniquely resolve short name reference to '{target_short_name}'") + elif expected_type is not None and not isinstance(candidates[0], expected_type): + odxraise(f"Reference '{target_short_name}' points to a {type(candidates[0]).__name__}" + f"object while expecting {expected_type.__name__}") + + return candidates[0] diff --git a/odxtools/parameters/parameterwithdop.py b/odxtools/parameters/parameterwithdop.py index 00ee8622..b5ab03aa 100644 --- a/odxtools/parameters/parameterwithdop.py +++ b/odxtools/parameters/parameterwithdop.py @@ -11,10 +11,10 @@ from ..dtcdop import DtcDop from ..encodestate import EncodeState from ..exceptions import odxassert, odxrequire -from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref from ..odxtypes import AtomicOdxType, ParameterValue from ..physicaltype import PhysicalType -from ..utils import dataclass_fields_asdict, resolve_snref +from ..utils import dataclass_fields_asdict from .parameter import Parameter if TYPE_CHECKING: diff --git a/odxtools/parameters/tablekeyparameter.py b/odxtools/parameters/tablekeyparameter.py index 66cce677..ca2b0175 100644 --- a/odxtools/parameters/tablekeyparameter.py +++ b/odxtools/parameters/tablekeyparameter.py @@ -8,9 +8,9 @@ from ..decodestate import DecodeState from ..encodestate import EncodeState from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire -from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref from ..odxtypes import ParameterValue -from ..utils import dataclass_fields_asdict, resolve_snref +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType if TYPE_CHECKING: diff --git a/odxtools/parameters/tablestructparameter.py b/odxtools/parameters/tablestructparameter.py index 48fb64f2..083da933 100644 --- a/odxtools/parameters/tablestructparameter.py +++ b/odxtools/parameters/tablestructparameter.py @@ -8,9 +8,9 @@ from ..decodestate import DecodeState from ..encodestate import EncodeState from ..exceptions import DecodeError, EncodeError, odxraise, odxrequire -from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef +from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref from ..odxtypes import ParameterValue -from ..utils import dataclass_fields_asdict, resolve_snref +from ..utils import dataclass_fields_asdict from .parameter import Parameter, ParameterType from .tablekeyparameter import TableKeyParameter diff --git a/odxtools/statechart.py b/odxtools/statechart.py index 83fcab58..bd8bc9cd 100644 --- a/odxtools/statechart.py +++ b/odxtools/statechart.py @@ -6,10 +6,10 @@ from .element import IdentifiableElement from .exceptions import odxrequire from .nameditemlist import NamedItemList -from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId +from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, resolve_snref from .state import State from .statetransition import StateTransition -from .utils import dataclass_fields_asdict, resolve_snref +from .utils import dataclass_fields_asdict if TYPE_CHECKING: from .diaglayer import DiagLayer diff --git a/odxtools/statetransition.py b/odxtools/statetransition.py index 54a68a1b..bcc6930f 100644 --- a/odxtools/statetransition.py +++ b/odxtools/statetransition.py @@ -5,9 +5,9 @@ from .element import IdentifiableElement from .exceptions import odxrequire -from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId +from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, resolve_snref from .state import State -from .utils import dataclass_fields_asdict, resolve_snref +from .utils import dataclass_fields_asdict if TYPE_CHECKING: from .diaglayer import DiagLayer diff --git a/odxtools/tablerow.py b/odxtools/tablerow.py index 6ba9d153..242a6614 100644 --- a/odxtools/tablerow.py +++ b/odxtools/tablerow.py @@ -9,10 +9,10 @@ from .dtcdop import DtcDop from .element import IdentifiableElement from .exceptions import odxassert, odxraise, odxrequire -from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef +from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref from .odxtypes import AtomicOdxType from .specialdatagroup import SpecialDataGroup -from .utils import dataclass_fields_asdict, resolve_snref +from .utils import dataclass_fields_asdict if TYPE_CHECKING: from .diaglayer import DiagLayer diff --git a/odxtools/utils.py b/odxtools/utils.py index 1ac9f100..d8e7aac8 100644 --- a/odxtools/utils.py +++ b/odxtools/utils.py @@ -1,12 +1,9 @@ # SPDX-License-Identifier: MIT import dataclasses import re -from typing import Any, Dict, Iterable, Optional, Type, overload +from typing import Any, Dict, Optional from xml.etree import ElementTree -from .exceptions import odxraise -from .nameditemlist import OdxNamed, TNamed - def create_description_from_et(et_element: Optional[ElementTree.Element],) -> Optional[str]: """Read a description tag. @@ -63,34 +60,3 @@ def is_short_name_path(test_val: str) -> bool: See also: ISO 22901 section 7.3.13.3 """ return _short_name_path_pattern.fullmatch(test_val) is not None - - -@overload -def resolve_snref(target_short_name: str, - items: Iterable[OdxNamed], - expected_type: None = None) -> Any: - """Properly resolve a short name reference""" - ... - - -@overload -def resolve_snref(target_short_name: str, items: Iterable[OdxNamed], - expected_type: Type[TNamed]) -> TNamed: - ... - - -def resolve_snref(target_short_name: str, - items: Iterable[OdxNamed], - expected_type: Any = None) -> Any: - candidates = [x for x in items if x.short_name == target_short_name] - - if not candidates: - odxraise(f"Cannot resolve short name reference to '{target_short_name}'") - return None - elif len(candidates) > 1: - odxraise(f"Cannot uniquely resolve short name reference to '{target_short_name}'") - elif expected_type is not None and not isinstance(candidates[0], expected_type): - odxraise(f"Reference '{target_short_name}' points to a {type(candidates[0]).__name__}" - f"object while expecting {expected_type.__name__}") - - return candidates[0]