Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions odxtools/configdata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass, field
from typing import Any
from xml.etree import ElementTree

from .configrecord import ConfigRecord
from .element import NamedElement
from .nameditemlist import NamedItemList
from .odxdoccontext import OdxDocContext
from .odxlink import OdxLinkDatabase, OdxLinkId
from .snrefcontext import SnRefContext
from .specialdatagroup import SpecialDataGroup
from .utils import dataclass_fields_asdict
from .validbasevariant import ValidBaseVariant


@dataclass(kw_only=True)
class ConfigData(NamedElement):
"""This class represents a CONFIG-DATA."""
valid_base_variants: list[ValidBaseVariant]
config_records: NamedItemList[ConfigRecord]
sdgs: list[SpecialDataGroup] = field(default_factory=list)

@staticmethod
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigData":
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))

valid_base_variants = [
ValidBaseVariant.from_et(vbv_elem, context)
for vbv_elem in et_element.iterfind("VALID-BASE-VARIANTS/VALID-BASE-VARIANT")
]
config_records = NamedItemList([
ConfigRecord.from_et(cr_elem, context)
for cr_elem in et_element.iterfind("CONFIG-RECORDS/CONFIG-RECORD")
])
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]

return ConfigData(
valid_base_variants=valid_base_variants,
config_records=config_records,
sdgs=sdgs,
**kwargs)

def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
result = {}

for valid_base_variant in self.valid_base_variants:
result.update(valid_base_variant._build_odxlinks())
for config_record in self.config_records:
result.update(config_record._build_odxlinks())
for sdg in self.sdgs:
result.update(sdg._build_odxlinks())

return result

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
for valid_base_variant in self.valid_base_variants:
valid_base_variant._resolve_odxlinks(odxlinks)
for config_record in self.config_records:
config_record._resolve_odxlinks(odxlinks)
for sdg in self.sdgs:
sdg._resolve_odxlinks(odxlinks)

def _resolve_snrefs(self, context: SnRefContext) -> None:
for valid_base_variant in self.valid_base_variants:
valid_base_variant._resolve_snrefs(context)
for config_record in self.config_records:
config_record._resolve_snrefs(context)
for sdg in self.sdgs:
sdg._resolve_snrefs(context)
57 changes: 57 additions & 0 deletions odxtools/configdatadictionaryspec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass, field
from typing import Any
from xml.etree import ElementTree

from .dataobjectproperty import DataObjectProperty
from .nameditemlist import NamedItemList
from .odxdoccontext import OdxDocContext
from .odxlink import OdxLinkDatabase, OdxLinkId
from .snrefcontext import SnRefContext
from .unitspec import UnitSpec


@dataclass(kw_only=True)
class ConfigDataDictionarySpec:
data_object_props: NamedItemList[DataObjectProperty] = field(default_factory=NamedItemList)
unit_spec: UnitSpec | None = None

@staticmethod
def from_et(et_element: ElementTree.Element,
context: OdxDocContext) -> "ConfigDataDictionarySpec":
data_object_props = NamedItemList([
DataObjectProperty.from_et(dop_element, context)
for dop_element in et_element.iterfind("DATA-OBJECT-PROPS/DATA-OBJECT-PROP")
])

if (spec_elem := et_element.find("UNIT-SPEC")) is not None:
unit_spec = UnitSpec.from_et(spec_elem, context)
else:
unit_spec = None

return ConfigDataDictionarySpec(
data_object_props=data_object_props,
unit_spec=unit_spec,
)

def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
odxlinks = {}

for data_object_prop in self.data_object_props:
odxlinks.update(data_object_prop._build_odxlinks())
if self.unit_spec is not None:
odxlinks.update(self.unit_spec._build_odxlinks())

return odxlinks

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
for data_object_prop in self.data_object_props:
data_object_prop._resolve_odxlinks(odxlinks)
if self.unit_spec is not None:
self.unit_spec._resolve_odxlinks(odxlinks)

def _resolve_snrefs(self, context: SnRefContext) -> None:
for data_object_prop in self.data_object_props:
data_object_prop._resolve_snrefs(context)
if self.unit_spec is not None:
self.unit_spec._resolve_snrefs(context)
18 changes: 18 additions & 0 deletions odxtools/configiditem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from xml.etree import ElementTree

from .configitem import ConfigItem
from .odxdoccontext import OdxDocContext
from .utils import dataclass_fields_asdict


@dataclass(kw_only=True)
class ConfigIdItem(ConfigItem):
"""This class represents a CONFIG-ID-ITEM."""

@staticmethod
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigIdItem":
kwargs = dataclass_fields_asdict(ConfigItem.from_et(et_element, context))

return ConfigIdItem(**kwargs)
85 changes: 85 additions & 0 deletions odxtools/configitem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass, field
from typing import Any
from xml.etree import ElementTree

from .dopbase import DopBase
from .element import NamedElement
from .exceptions import odxrequire
from .odxdoccontext import OdxDocContext
from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
from .snrefcontext import SnRefContext
from .specialdatagroup import SpecialDataGroup
from .utils import dataclass_fields_asdict


@dataclass(kw_only=True)
class ConfigItem(NamedElement):
"""This class represents a CONFIG-ITEM.

CONFIG-ITEM is the base class for CONFIG-ID-ITEM, DATA-ID-ITEM,
OPTION-ITEM, and SYSTEM-ITEM.
"""
byte_position: int | None = None
bit_position: int | None = None

# according to the spec exactly one of the following two
# attributes is not None...x
data_object_prop_ref: OdxLinkRef | None = None
data_object_prop_snref: str | None = None
sdgs: list[SpecialDataGroup] = field(default_factory=list)

@property
def data_object_prop(self) -> DopBase:
return self._data_object_prop

@staticmethod
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigItem":
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))

byte_position = None
if (byte_pos_elem := et_element.findtext("BYTE-POSITION")) is not None:
byte_position = int(byte_pos_elem)

bit_position = None
if (bit_pos_elem := et_element.findtext("BIT-POSITION")) is not None:
bit_position = int(bit_pos_elem)

data_object_prop_ref = OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), context)
data_object_prop_snref = None
if (data_object_prop_snref_elem := et_element.find("DATA-OBJECT-PROP-SNREF")) is not None:
data_object_prop_snref = odxrequire(
data_object_prop_snref_elem.attrib.get("SHORT-NAME"))
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]

return ConfigItem(
byte_position=byte_position,
bit_position=bit_position,
data_object_prop_ref=data_object_prop_ref,
data_object_prop_snref=data_object_prop_snref,
sdgs=sdgs,
**kwargs)

def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
result = {}

for sdg in self.sdgs:
result.update(sdg._build_odxlinks())

return result

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
if self.data_object_prop_ref is not None:
self._data_object_prop = odxlinks.resolve(self.data_object_prop_ref, DopBase)

for sdg in self.sdgs:
sdg._resolve_odxlinks(odxlinks)

def _resolve_snrefs(self, context: SnRefContext) -> None:
if self.data_object_prop_snref is not None:
ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
self._data_object_prop = resolve_snref(self.data_object_prop_snref,
ddds.all_data_object_properties, DopBase)

for sdg in self.sdgs:
sdg._resolve_snrefs(context)
146 changes: 146 additions & 0 deletions odxtools/configrecord.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass, field
from typing import Any
from xml.etree import ElementTree

from .audience import Audience
from .configiditem import ConfigIdItem
from .dataiditem import DataIdItem
from .datarecord import DataRecord
from .diagcommdataconnector import DiagCommDataConnector
from .element import NamedElement
from .exceptions import odxrequire
from .identvalue import IdentValue
from .nameditemlist import NamedItemList
from .odxdoccontext import OdxDocContext
from .odxlink import OdxLinkDatabase, OdxLinkId
from .optionitem import OptionItem
from .snrefcontext import SnRefContext
from .specialdatagroup import SpecialDataGroup
from .systemitem import SystemItem
from .utils import dataclass_fields_asdict


@dataclass(kw_only=True)
class ConfigRecord(NamedElement):
"""This class represents a CONFIG-RECORD."""
config_id_item: ConfigIdItem | None = None
diag_comm_data_connectors: list[DiagCommDataConnector] = field(default_factory=list)
config_id: IdentValue | None = None
data_records: NamedItemList[DataRecord] = field(default_factory=NamedItemList)
audience: Audience | None = None
system_items: NamedItemList[SystemItem] = field(default_factory=NamedItemList)
data_id_item: DataIdItem | None = None
option_items: NamedItemList[OptionItem] = field(default_factory=NamedItemList)
default_data_record_snref: str | None = None
sdgs: list[SpecialDataGroup] = field(default_factory=list)

@staticmethod
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigRecord":
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))

config_id_item = None
if (cid_elem := et_element.find("CONFIG-ID-ITEM")) is not None:
config_id_item = ConfigIdItem.from_et(cid_elem, context)
diag_comm_data_connectors = [
DiagCommDataConnector.from_et(dcdc_elem, context) for dcdc_elem in et_element.iterfind(
"DIAG-COMM-DATA-CONNECTORS/DIAG-COMM-DATA-CONNECTOR")
]
config_id = None
if (cid_elem := et_element.find("CONFIG-ID")) is not None:
config_id = IdentValue.from_et(cid_elem, context)
data_records = NamedItemList([
DataRecord.from_et(dr_elem, context)
for dr_elem in et_element.iterfind("DATA-RECORDS/DATA-RECORD")
])
audience = None
if (aud_elem := et_element.find("AUDIENCE")) is not None:
audience = Audience.from_et(aud_elem, context)
system_items = NamedItemList([
SystemItem.from_et(si_elem, context)
for si_elem in et_element.iterfind("SYSTEM-ITEMS/SYSTEM-ITEM")
])
data_id_item = None
if (dii_elem := et_element.find("DATA-ID-ITEM")) is not None:
data_id_item = DataIdItem.from_et(dii_elem, context)
option_items = NamedItemList([
OptionItem.from_et(si_elem, context)
for si_elem in et_element.iterfind("OPTION-ITEMS/OPTION-ITEM")
])
default_data_record_snref = None
if (default_data_record_snref_elem :=
et_element.find("DEFAULT-DATA-RECORD-SNREF")) is not None:
default_data_record_snref = odxrequire(
default_data_record_snref_elem.attrib.get("SHORT-NAME"))
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]

return ConfigRecord(
config_id_item=config_id_item,
diag_comm_data_connectors=diag_comm_data_connectors,
config_id=config_id,
data_records=data_records,
audience=audience,
system_items=system_items,
data_id_item=data_id_item,
option_items=option_items,
default_data_record_snref=default_data_record_snref,
sdgs=sdgs,
**kwargs)

def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
result = {}

if self.config_id_item is not None:
result.update(self.config_id_item._build_odxlinks())
for diag_comm_data_connector in self.diag_comm_data_connectors:
result.update(diag_comm_data_connector._build_odxlinks())
for data_record in self.data_records:
result.update(data_record._build_odxlinks())
if self.audience is not None:
result.update(self.audience._build_odxlinks())
for system_item in self.system_items:
result.update(system_item._build_odxlinks())
if self.data_id_item is not None:
result.update(self.data_id_item._build_odxlinks())
for option_item in self.option_items:
result.update(option_item._build_odxlinks())
for sdg in self.sdgs:
result.update(sdg._build_odxlinks())

return result
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit off topic, but maybe _build_odxlinks, _resolve_odxlinks and _resolve_snrefs, can be simplified but have one function called _get_referenceable(self) -> list[Optional[LinkableElement]], that return all the objects that should have _build_odxlinks, _resolve_odxlinks and _resolve_snrefs called on them, the rest of the logic can be implemented in the base class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the "leafs" of _build_odxlinks() should be moved into IdentifiableElement (#425). For resolving the references, I don't really see a good alternative to the current solution. Maybe we can introduce a proxy class like weakref.proxy but I'm not sure if this such a terribly good idea as it is quite "magical"...


def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
if self.config_id_item is not None:
self.config_id_item._resolve_odxlinks(odxlinks)
for diag_comm_data_connector in self.diag_comm_data_connectors:
diag_comm_data_connector._resolve_odxlinks(odxlinks)
for data_record in self.data_records:
data_record._resolve_odxlinks(odxlinks)
if self.audience is not None:
self.audience._resolve_odxlinks(odxlinks)
for system_item in self.system_items:
system_item._resolve_odxlinks(odxlinks)
if self.data_id_item is not None:
self.data_id_item._resolve_odxlinks(odxlinks)
for option_item in self.option_items:
option_item._resolve_odxlinks(odxlinks)
for sdg in self.sdgs:
sdg._resolve_odxlinks(odxlinks)

def _resolve_snrefs(self, context: SnRefContext) -> None:
if self.config_id_item is not None:
self.config_id_item._resolve_snrefs(context)
for diag_comm_data_connector in self.diag_comm_data_connectors:
diag_comm_data_connector._resolve_snrefs(context)
for data_record in self.data_records:
data_record._resolve_snrefs(context)
if self.audience is not None:
self.audience._resolve_snrefs(context)
for system_item in self.system_items:
system_item._resolve_snrefs(context)
if self.data_id_item is not None:
self.data_id_item._resolve_snrefs(context)
for option_item in self.option_items:
option_item._resolve_snrefs(context)
for sdg in self.sdgs:
sdg._resolve_snrefs(context)
Loading