Skip to content

Convert OdxLinkId.doc_fragments and OdxLinkRef.ref_docs to tuple #410

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
22 changes: 13 additions & 9 deletions examples/somersaultecu.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ class SomersaultSID(IntEnum):
dlc_short_name = "somersault"

# document fragment for everything except the communication parameters
doc_frags = [OdxDocFragment(dlc_short_name, DocType.CONTAINER)]
doc_frags = (OdxDocFragment(dlc_short_name, DocType.CONTAINER),)

# document fragments for communication parameters
cp_dwcan_doc_frags = [OdxDocFragment("ISO_11898_2_DWCAN", DocType.COMPARAM_SUBSET)]
cp_iso15765_2_doc_frags = [OdxDocFragment("ISO_15765_2", DocType.COMPARAM_SUBSET)]
cp_iso15765_3_doc_frags = [OdxDocFragment("ISO_15765_3", DocType.COMPARAM_SUBSET)]
cp_dwcan_doc_frags = (OdxDocFragment("ISO_11898_2_DWCAN", DocType.COMPARAM_SUBSET),)
cp_iso15765_2_doc_frags = (OdxDocFragment("ISO_15765_2", DocType.COMPARAM_SUBSET),)
cp_iso15765_3_doc_frags = (OdxDocFragment("ISO_15765_3", DocType.COMPARAM_SUBSET),)

##################
# Base variant of Somersault ECU
Expand Down Expand Up @@ -1430,9 +1430,8 @@ class SomersaultSID(IntEnum):
long_name="Somersault protocol info",
description=Description.from_string(
"<p>Protocol information of the somersault ECUs &amp; cetera</p>"),
comparam_spec_ref=OdxLinkRef("CPS_ISO_15765_3_on_ISO_15765_2", [
OdxDocFragment("ISO_15765_3_on_ISO_15765_2", DocType.COMPARAM_SPEC)
]),
comparam_spec_ref=OdxLinkRef("CPS_ISO_15765_3_on_ISO_15765_2", (OdxDocFragment(
"ISO_15765_3_on_ISO_15765_2", DocType.COMPARAM_SPEC),)),
comparam_refs=somersault_comparam_refs,
)
somersault_protocol = Protocol(diag_layer_raw=somersault_protocol_raw)
Expand Down Expand Up @@ -1764,14 +1763,19 @@ class SomersaultSID(IntEnum):
odx_cs_root = ElementTree.parse(odx_cs_dir / odx_cs_filename).getroot()
subset = odx_cs_root.find("COMPARAM-SUBSET")
if subset is not None:
comparam_subsets.append(ComparamSubset.from_et(subset, OdxDocContext(ODX_VERSION, [])))
category_sn = odxrequire(subset.findtext("SHORT-NAME"))
context = OdxDocContext(ODX_VERSION,
(OdxDocFragment(category_sn, DocType.COMPARAM_SUBSET),))
comparam_subsets.append(ComparamSubset.from_et(subset, context))

comparam_specs = []
for odx_c_filename in ("UDSOnCAN_CPS.odx-c",):
odx_c_root = ElementTree.parse(odx_cs_dir / odx_c_filename).getroot()
subset = odx_c_root.find("COMPARAM-SPEC")
if subset is not None:
comparam_specs.append(ComparamSpec.from_et(subset, OdxDocContext(ODX_VERSION, [])))
category_sn = odxrequire(subset.findtext("SHORT-NAME"))
context = OdxDocContext(ODX_VERSION, (OdxDocFragment(category_sn, DocType.COMPARAM_SPEC),))
comparam_specs.append(ComparamSpec.from_et(subset, context))

# create a database object
database = Database()
Expand Down
4 changes: 2 additions & 2 deletions odxtools/comparamspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .nameditemlist import NamedItemList
from .odxcategory import OdxCategory
from .odxdoccontext import OdxDocContext
from .odxlink import DocType, OdxLinkDatabase, OdxLinkId
from .odxlink import OdxLinkDatabase, OdxLinkId
from .protstack import ProtStack
from .snrefcontext import SnRefContext
from .utils import dataclass_fields_asdict
Expand All @@ -23,7 +23,7 @@ class ComparamSpec(OdxCategory):
@staticmethod
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ComparamSpec":

base_obj = OdxCategory.category_from_et(et_element, context, doc_type=DocType.COMPARAM_SPEC)
base_obj = OdxCategory.from_et(et_element, context)
kwargs = dataclass_fields_asdict(base_obj)

prot_stacks = NamedItemList([
Expand Down
11 changes: 2 additions & 9 deletions odxtools/comparamsubset.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .nameditemlist import NamedItemList
from .odxcategory import OdxCategory
from .odxdoccontext import OdxDocContext
from .odxlink import DocType, OdxLinkDatabase, OdxLinkId
from .odxlink import OdxLinkDatabase, OdxLinkId
from .snrefcontext import SnRefContext
from .unitspec import UnitSpec
from .utils import dataclass_fields_asdict
Expand All @@ -31,14 +31,7 @@ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Compara

category_attrib = et_element.attrib.get("CATEGORY")

# In ODX 2.0, COMPARAM-SPEC is used, whereas in ODX 2.2, it
# refers to something else and has been replaced by
# COMPARAM-SUBSET.
# - If the 'CATEGORY' attribute is missing (ODX 2.0), use
# COMPARAM_SPEC,
# - else (ODX 2.2), use COMPARAM_SUBSET.
doc_type = DocType.COMPARAM_SUBSET if category_attrib is not None else DocType.COMPARAM_SPEC
base_obj = OdxCategory.category_from_et(et_element, context, doc_type=doc_type)
base_obj = OdxCategory.from_et(et_element, context)
kwargs = dataclass_fields_asdict(base_obj)

comparams = NamedItemList(
Expand Down
56 changes: 27 additions & 29 deletions odxtools/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from .exceptions import odxraise, odxrequire
from .nameditemlist import NamedItemList
from .odxdoccontext import OdxDocContext
from .odxlink import OdxLinkDatabase, OdxLinkId
from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
from .snrefcontext import SnRefContext


Expand Down Expand Up @@ -77,10 +77,6 @@ def add_auxiliary_file(self,
self.auxiliary_files[str(aux_file_name)] = aux_file_obj

def _process_xml_tree(self, root: ElementTree.Element) -> None:
dlcs: list[DiagLayerContainer] = []
comparam_subsets: list[ComparamSubset] = []
comparam_specs: list[ComparamSpec] = []

# ODX spec version
model_version = Version(root.attrib.get("MODEL-VERSION", "2.0"))
if self.model_version is not None and self.model_version != model_version:
Expand All @@ -89,31 +85,33 @@ def _process_xml_tree(self, root: ElementTree.Element) -> None:

self.model_version = model_version

dlc = root.find("DIAG-LAYER-CONTAINER")
if dlc is not None:
dlcs.append(DiagLayerContainer.from_et(dlc, OdxDocContext(model_version, [])))

# In ODX 2.0 there was only COMPARAM-SPEC. In ODX 2.2 the
# content of COMPARAM-SPEC was moved to COMPARAM-SUBSET
# and COMPARAM-SPEC became a container for PROT-STACKS and
# a PROT-STACK references a list of COMPARAM-SUBSET
cp_subset = root.find("COMPARAM-SUBSET")
if cp_subset is not None:
comparam_subsets.append(
ComparamSubset.from_et(cp_subset, OdxDocContext(model_version, [])))

cp_spec = root.find("COMPARAM-SPEC")
if cp_spec is not None:
child_elements = list(root)
if len(child_elements) != 1:
odxraise("Each ODX document must contain exactly one category.")

category_et = child_elements[0]
category_sn = odxrequire(category_et.findtext("SHORT-NAME"))
category_tag = category_et.tag

if category_tag == "DIAG-LAYER-CONTAINER":
context = OdxDocContext(model_version,
(OdxDocFragment(category_sn, DocType.CONTAINER),))
self._diag_layer_containers.append(DiagLayerContainer.from_et(category_et, context))
elif category_tag == "COMPARAM-SUBSET":
context = OdxDocContext(model_version,
(OdxDocFragment(category_sn, DocType.COMPARAM_SUBSET),))
self._comparam_subsets.append(ComparamSubset.from_et(category_et, context))
elif category_tag == "COMPARAM-SPEC":
# In ODX 2.0 there was only COMPARAM-SPEC. In ODX 2.2 the
# content of COMPARAM-SPEC was moved to COMPARAM-SUBSET
# and COMPARAM-SPEC became a container for PROT-STACKS and
# a PROT-STACK references a list of COMPARAM-SUBSET
context = OdxDocContext(model_version,
(OdxDocFragment(category_sn, DocType.COMPARAM_SPEC),))
if model_version < Version("2.2"):
comparam_subsets.append(
ComparamSubset.from_et(cp_spec, OdxDocContext(model_version, [])))
else: # odx >= 2.2
comparam_specs.append(
ComparamSpec.from_et(cp_spec, OdxDocContext(model_version, [])))

self._diag_layer_containers.extend(dlcs)
self._comparam_subsets.extend(comparam_subsets)
self._comparam_specs.extend(comparam_specs)
self._comparam_subsets.append(ComparamSubset.from_et(category_et, context))
else:
self._comparam_specs.append(ComparamSpec.from_et(category_et, context))

def refresh(self) -> None:
# Create wrapper objects
Expand Down
46 changes: 24 additions & 22 deletions odxtools/diaglayercontainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
from .diaglayers.ecuvariant import EcuVariant
from .diaglayers.functionalgroup import FunctionalGroup
from .diaglayers.protocol import Protocol
from .exceptions import odxrequire
from .nameditemlist import NamedItemList
from .odxcategory import OdxCategory
from .odxdoccontext import OdxDocContext
from .odxlink import DocType, OdxLinkDatabase, OdxLinkId
from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
from .snrefcontext import SnRefContext
from .utils import dataclass_fields_asdict

Expand Down Expand Up @@ -43,29 +44,30 @@ def ecus(self) -> NamedItemList[EcuVariant]:
@staticmethod
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "DiagLayerContainer":

cat = OdxCategory.category_from_et(et_element, context, doc_type=DocType.CONTAINER)
cat = OdxCategory.from_et(et_element, context)
kwargs = dataclass_fields_asdict(cat)

protocols = NamedItemList([
Protocol.from_et(dl_element, context)
for dl_element in et_element.iterfind("PROTOCOLS/PROTOCOL")
])
functional_groups = NamedItemList([
FunctionalGroup.from_et(dl_element, context)
for dl_element in et_element.iterfind("FUNCTIONAL-GROUPS/FUNCTIONAL-GROUP")
])
ecu_shared_datas = NamedItemList([
EcuSharedData.from_et(dl_element, context)
for dl_element in et_element.iterfind("ECU-SHARED-DATAS/ECU-SHARED-DATA")
])
base_variants = NamedItemList([
BaseVariant.from_et(dl_element, context)
for dl_element in et_element.iterfind("BASE-VARIANTS/BASE-VARIANT")
])
ecu_variants = NamedItemList([
EcuVariant.from_et(dl_element, context)
for dl_element in et_element.iterfind("ECU-VARIANTS/ECU-VARIANT")
])
def get_layer_context(diag_layer_et: ElementTree.Element) -> OdxDocContext:
layer_sn = odxrequire(diag_layer_et.findtext("SHORT-NAME"))
layer_docfrag = OdxDocFragment(layer_sn, DocType.LAYER)
# add layer doc fragment to container doc fragment
return OdxDocContext(context.version, (context.doc_fragments[0], layer_docfrag))

protocols = NamedItemList(
Protocol.from_et(layer_et, get_layer_context(layer_et))
for layer_et in et_element.iterfind("PROTOCOLS/PROTOCOL"))
functional_groups = NamedItemList(
FunctionalGroup.from_et(layer_et, get_layer_context(layer_et))
for layer_et in et_element.iterfind("FUNCTIONAL-GROUPS/FUNCTIONAL-GROUP"))
ecu_shared_datas = NamedItemList(
EcuSharedData.from_et(layer_et, get_layer_context(layer_et))
for layer_et in et_element.iterfind("ECU-SHARED-DATAS/ECU-SHARED-DATA"))
base_variants = NamedItemList(
BaseVariant.from_et(layer_et, get_layer_context(layer_et))
for layer_et in et_element.iterfind("BASE-VARIANTS/BASE-VARIANT"))
ecu_variants = NamedItemList(
EcuVariant.from_et(layer_et, get_layer_context(layer_et))
for layer_et in et_element.iterfind("ECU-VARIANTS/ECU-VARIANT"))

return DiagLayerContainer(
protocols=protocols,
Expand Down
8 changes: 2 additions & 6 deletions odxtools/diaglayers/diaglayerraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
from ..diagdatadictionaryspec import DiagDataDictionarySpec
from ..diagservice import DiagService
from ..element import IdentifiableElement
from ..exceptions import odxassert, odxraise, odxrequire
from ..exceptions import odxassert, odxraise
from ..functionalclass import FunctionalClass
from ..library import Library
from ..nameditemlist import NamedItemList
from ..odxdoccontext import OdxDocContext
from ..odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
from ..odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
from ..request import Request
from ..response import Response
from ..singleecujob import SingleEcuJob
Expand Down Expand Up @@ -77,10 +77,6 @@ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "DiagLay
variant_type = cast(DiagLayerType, None)
odxraise(f"Encountered unknown diagnostic layer type '{et_element.tag}'")

short_name = odxrequire(et_element.findtext("SHORT-NAME"))

# extend the applicable ODX "document fragments" for the diag layer objects
context.doc_fragments.append(OdxDocFragment(short_name, DocType.LAYER))
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))

admin_data = None
Expand Down
2 changes: 1 addition & 1 deletion odxtools/diaglayers/hierarchyelement.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from .hierarchyelementraw import HierarchyElementRaw

if TYPE_CHECKING:
from .database import Database
from ..database import Database
Copy link
Collaborator

Choose a reason for hiding this comment

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

mypy didn't catch this ?

Copy link
Member

Choose a reason for hiding this comment

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

@zariiii9003: how did you find this? if using tools, can/should we change the CI system to do the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I noticed red squiggly lines in my IDE. I also wonder why both ruff and mypy seem to ignore it

Copy link
Member

Choose a reason for hiding this comment

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

ok. Seems like you should continue to let your IDE look at odxtools. mine (emacs) is not sophisticated enough ;)

from .protocol import Protocol

TNamed = TypeVar("TNamed", bound=OdxNamed)
Expand Down
13 changes: 1 addition & 12 deletions odxtools/odxcategory.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
from .admindata import AdminData
from .companydata import CompanyData
from .element import IdentifiableElement
from .exceptions import odxrequire
from .nameditemlist import NamedItemList
from .odxdoccontext import OdxDocContext
from .odxlink import DocType, OdxDocFragment, OdxLinkDatabase, OdxLinkId
from .odxlink import OdxLinkDatabase, OdxLinkId
from .snrefcontext import SnRefContext
from .specialdatagroup import SpecialDataGroup
from .utils import dataclass_fields_asdict
Expand All @@ -28,17 +27,7 @@ class OdxCategory(IdentifiableElement):

@staticmethod
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "OdxCategory":
raise Exception("Calling `._from_et()` is not allowed for OdxCategory. "
"Use `OdxCategory.category_from_et()`!")

@staticmethod
def category_from_et(et_element: ElementTree.Element, context: OdxDocContext, *,
doc_type: DocType) -> "OdxCategory":

short_name = odxrequire(et_element.findtext("SHORT-NAME"))
# create the current ODX "document fragment" (description of the
# current document for references and IDs)
context.doc_fragments.append(OdxDocFragment(short_name, doc_type))
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))

admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), context)
Expand Down
11 changes: 8 additions & 3 deletions odxtools/odxdoccontext.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from typing import TYPE_CHECKING, NamedTuple
from dataclasses import dataclass
from typing import TYPE_CHECKING

from packaging.version import Version

if TYPE_CHECKING:
from odxtools.odxlink import OdxDocFragment


class OdxDocContext(NamedTuple):
@dataclass(slots=True, frozen=True)
class OdxDocContext:
version: Version
doc_fragments: list["OdxDocFragment"]

# the doc_fragments are either tuple(doc_frag(category),)
# or tuple(doc_frag(category), doc_frag(diag_layer))
doc_fragments: tuple["OdxDocFragment"] | tuple["OdxDocFragment", "OdxDocFragment"]
12 changes: 6 additions & 6 deletions odxtools/odxlink.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: MIT
import warnings
from collections.abc import Iterable
from dataclasses import dataclass, field
from dataclasses import dataclass
from enum import Enum
from typing import Any, Optional, TypeVar, overload
from xml.etree import ElementTree
Expand Down Expand Up @@ -46,7 +46,7 @@ class OdxLinkId:

#: The name and type of the document fragment to which the
#: `local_id` is relative to
doc_fragments: list[OdxDocFragment] = field(default_factory=list)
doc_fragments: tuple[OdxDocFragment] | tuple[OdxDocFragment, OdxDocFragment]

def __hash__(self) -> int:
# we do not hash about the document fragment here, because
Expand Down Expand Up @@ -95,7 +95,7 @@ class OdxLinkRef:
ref_id: str

#: The document fragments to which the `ref_id` refers to (in reverse order)
ref_docs: list[OdxDocFragment] = field(default_factory=list)
ref_docs: tuple[OdxDocFragment, ...]

# TODO: this is difficult because OdxLinkRef is derived from and
# we do not want having to specify it mandatorily
Expand Down Expand Up @@ -143,10 +143,10 @@ def from_et(et: ElementTree.Element | None, context: OdxDocContext) -> Optional[
# if the target document fragment is specified by the
# reference, use it, else use the document fragment containing
# the reference.
if docref is not None:
doc_frags = [OdxDocFragment(docref, odxrequire(doctype))]
else:
if docref is None:
doc_frags = context.doc_fragments
else:
doc_frags = (OdxDocFragment(docref, odxrequire(doctype)),)

return OdxLinkRef(id_ref, doc_frags)

Expand Down
4 changes: 3 additions & 1 deletion odxtools/templates/macros/printParentRef.xml.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

{%- macro printParentRef(par) -%}
<PARENT-REF ID-REF="{{par.layer.odx_id.local_id}}"
DOCREF="{{get_parent_container_name(par.layer.short_name)}}"
{%- if par.layer_ref.ref_docs|length == 1 %}
DOCREF="{{par.layer_ref.ref_docs[0].doc_name}}"
DOCTYPE="CONTAINER"
{%- endif %}
xsi:type="{{par.layer.variant_type.value}}-REF">
{%- if par.not_inherited_diag_comms %}
<NOT-INHERITED-DIAG-COMMS>
Expand Down
Loading