odxtools 10.1.0__py3-none-any.whl → 10.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. odxtools/addrdeffilter.py +33 -0
  2. odxtools/addrdefphyssegment.py +33 -0
  3. odxtools/checksum.py +67 -0
  4. odxtools/checksumresult.py +7 -0
  5. odxtools/cli/compare.py +143 -170
  6. odxtools/database.py +24 -4
  7. odxtools/datablock.py +153 -0
  8. odxtools/datafile.py +23 -0
  9. odxtools/dataformat.py +39 -0
  10. odxtools/dataformatselection.py +9 -0
  11. odxtools/description.py +2 -5
  12. odxtools/diagdatadictionaryspec.py +1 -3
  13. odxtools/direction.py +7 -0
  14. odxtools/ecumem.py +71 -0
  15. odxtools/ecumemconnector.py +136 -0
  16. odxtools/encryptcompressmethod.py +39 -0
  17. odxtools/encryptcompressmethodtype.py +13 -0
  18. odxtools/expectedident.py +40 -0
  19. odxtools/externflashdata.py +34 -0
  20. odxtools/filter.py +32 -0
  21. odxtools/flash.py +88 -0
  22. odxtools/flashclass.py +32 -0
  23. odxtools/flashdata.py +70 -0
  24. odxtools/fwchecksum.py +7 -0
  25. odxtools/fwsignature.py +7 -0
  26. odxtools/identdesc.py +54 -0
  27. odxtools/identvalue.py +32 -0
  28. odxtools/identvaluetype.py +14 -0
  29. odxtools/internflashdata.py +33 -0
  30. odxtools/loadfile.py +1 -1
  31. odxtools/mem.py +80 -0
  32. odxtools/modification.py +3 -2
  33. odxtools/negoffset.py +21 -0
  34. odxtools/ownident.py +38 -0
  35. odxtools/physicaltype.py +12 -10
  36. odxtools/physmem.py +52 -0
  37. odxtools/physsegment.py +42 -0
  38. odxtools/posoffset.py +21 -0
  39. odxtools/security.py +42 -0
  40. odxtools/securitymethod.py +7 -0
  41. odxtools/segment.py +63 -0
  42. odxtools/session.py +88 -0
  43. odxtools/sessiondesc.py +101 -0
  44. odxtools/sessionsubelemtype.py +14 -0
  45. odxtools/sizedeffilter.py +33 -0
  46. odxtools/sizedefphyssegment.py +33 -0
  47. odxtools/specialdata.py +2 -1
  48. odxtools/targetaddroffset.py +13 -0
  49. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +1 -0
  50. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +1 -0
  51. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +2 -1
  52. odxtools/templates/flash.odx-f.xml.jinja2 +42 -0
  53. odxtools/templates/macros/printAdminData.xml.jinja2 +4 -4
  54. odxtools/templates/macros/printAudience.xml.jinja2 +3 -3
  55. odxtools/templates/macros/printChecksum.xml.jinja2 +36 -0
  56. odxtools/templates/macros/printComparam.xml.jinja2 +1 -1
  57. odxtools/templates/macros/printComparamRef.xml.jinja2 +1 -3
  58. odxtools/templates/macros/printCompuMethod.xml.jinja2 +1 -1
  59. odxtools/templates/macros/printDOP.xml.jinja2 +3 -3
  60. odxtools/templates/macros/printDatablock.xml.jinja2 +78 -0
  61. odxtools/templates/macros/printDiagComm.xml.jinja2 +2 -2
  62. odxtools/templates/macros/printDiagLayer.xml.jinja2 +2 -1
  63. odxtools/templates/macros/printDiagVariable.xml.jinja2 +4 -4
  64. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +3 -3
  65. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +2 -2
  66. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +2 -2
  67. odxtools/templates/macros/printEcuMem.xml.jinja2 +24 -0
  68. odxtools/templates/macros/printEcuMemConnector.xml.jinja2 +58 -0
  69. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  70. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  71. odxtools/templates/macros/printExpectedIdent.xml.jinja2 +21 -0
  72. odxtools/templates/macros/printFlashdata.xml.jinja2 +43 -0
  73. odxtools/templates/macros/printIdentDesc.xml.jinja2 +17 -0
  74. odxtools/templates/macros/printMem.xml.jinja2 +35 -0
  75. odxtools/templates/macros/printMux.xml.jinja2 +3 -3
  76. odxtools/templates/macros/printOdxCategory.xml.jinja2 +4 -4
  77. odxtools/templates/macros/printOwnIdent.xml.jinja2 +17 -0
  78. odxtools/templates/macros/printParam.xml.jinja2 +4 -4
  79. odxtools/templates/macros/printParentRef.xml.jinja2 +1 -5
  80. odxtools/templates/macros/printPhysMem.xml.jinja2 +20 -0
  81. odxtools/templates/macros/printPhysSegment.xml.jinja2 +33 -0
  82. odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +1 -1
  83. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  84. odxtools/templates/macros/printProtocol.xml.jinja2 +1 -1
  85. odxtools/templates/macros/printSecurity.xml.jinja2 +37 -0
  86. odxtools/templates/macros/printSegment.xml.jinja2 +31 -0
  87. odxtools/templates/macros/printService.xml.jinja2 +3 -3
  88. odxtools/templates/macros/printSession.xml.jinja2 +45 -0
  89. odxtools/templates/macros/printSessionDesc.xml.jinja2 +40 -0
  90. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +3 -3
  91. odxtools/templates/macros/printSpecialData.xml.jinja2 +2 -2
  92. odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +1 -1
  93. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  94. odxtools/templates/macros/printSubComponent.xml.jinja2 +3 -3
  95. odxtools/templates/macros/printTable.xml.jinja2 +6 -6
  96. odxtools/templates/macros/printUnitSpec.xml.jinja2 +2 -2
  97. odxtools/text.py +2 -6
  98. odxtools/utils.py +22 -1
  99. odxtools/validityfor.py +30 -0
  100. odxtools/version.py +2 -2
  101. odxtools/writepdxfile.py +70 -21
  102. {odxtools-10.1.0.dist-info → odxtools-10.2.0.dist-info}/METADATA +1 -1
  103. {odxtools-10.1.0.dist-info → odxtools-10.2.0.dist-info}/RECORD +107 -50
  104. {odxtools-10.1.0.dist-info → odxtools-10.2.0.dist-info}/WHEEL +1 -1
  105. {odxtools-10.1.0.dist-info → odxtools-10.2.0.dist-info}/entry_points.txt +0 -0
  106. {odxtools-10.1.0.dist-info → odxtools-10.2.0.dist-info}/licenses/LICENSE +0 -0
  107. {odxtools-10.1.0.dist-info → odxtools-10.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,33 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .exceptions import odxrequire
7
+ from .filter import Filter
8
+ from .odxdoccontext import OdxDocContext
9
+ from .odxlink import OdxLinkDatabase, OdxLinkId
10
+ from .snrefcontext import SnRefContext
11
+ from .utils import dataclass_fields_asdict, read_hex_binary
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class AddrdefFilter(Filter):
16
+ filter_end: int
17
+
18
+ @staticmethod
19
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "AddrdefFilter":
20
+ kwargs = dataclass_fields_asdict(Filter.from_et(et_element, context))
21
+
22
+ filter_end = odxrequire(read_hex_binary(et_element.find("FILTER-END")))
23
+
24
+ return AddrdefFilter(filter_end=filter_end, **kwargs)
25
+
26
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
27
+ return super()._build_odxlinks()
28
+
29
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
30
+ super()._resolve_odxlinks(odxlinks)
31
+
32
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
33
+ super()._resolve_snrefs(context)
@@ -0,0 +1,33 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .exceptions import odxrequire
7
+ from .odxdoccontext import OdxDocContext
8
+ from .odxlink import OdxLinkDatabase, OdxLinkId
9
+ from .physsegment import PhysSegment
10
+ from .snrefcontext import SnRefContext
11
+ from .utils import dataclass_fields_asdict, read_hex_binary
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class AddrdefPhysSegment(PhysSegment):
16
+ end_address: int
17
+
18
+ @staticmethod
19
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "AddrdefPhysSegment":
20
+ kwargs = dataclass_fields_asdict(PhysSegment.from_et(et_element, context))
21
+
22
+ end_address = odxrequire(read_hex_binary(et_element.find("END-ADDRESS")))
23
+
24
+ return AddrdefPhysSegment(end_address=end_address, **kwargs)
25
+
26
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
27
+ return super()._build_odxlinks()
28
+
29
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
30
+ super()._resolve_odxlinks(odxlinks)
31
+
32
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
33
+ super()._resolve_snrefs(context)
odxtools/checksum.py ADDED
@@ -0,0 +1,67 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .checksumresult import ChecksumResult
7
+ from .element import IdentifiableElement
8
+ from .exceptions import odxrequire
9
+ from .odxdoccontext import OdxDocContext
10
+ from .odxlink import OdxLinkDatabase, OdxLinkId
11
+ from .snrefcontext import SnRefContext
12
+ from .utils import dataclass_fields_asdict, read_hex_binary
13
+
14
+
15
+ @dataclass(kw_only=True)
16
+ class Checksum(IdentifiableElement):
17
+ fillbyte: int | None = None
18
+ source_start_address: int
19
+ compressed_size: int | None = None
20
+ checksum_alg: str | None = None
21
+
22
+ # exactly one of the two next fields must be not None
23
+ source_end_address: int | None = None
24
+ uncompressed_size: int | None = None
25
+
26
+ checksum_result: ChecksumResult
27
+
28
+ @staticmethod
29
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Checksum":
30
+
31
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
32
+
33
+ fillbyte = read_hex_binary(et_element.find("FILLBYTE"))
34
+ source_start_address = odxrequire(read_hex_binary(et_element.find("SOURCE-START-ADDRESS")))
35
+ compressed_size = None
36
+ if (cs_elem := et_element.find("COMPRESSED-SIZE")) is not None:
37
+ compressed_size = int(odxrequire(cs_elem.text) or "0")
38
+ checksum_alg = et_element.findtext("CHECKSUM-ALG")
39
+
40
+ # exactly one of the two next fields must be not None
41
+ source_end_address = read_hex_binary(et_element.find("SOURCE-END-ADDRESS"))
42
+ uncompressed_size = None
43
+ if (ucs_elem := et_element.find("UNCOMPRESSED-SIZE")) is not None:
44
+ uncompressed_size = int(odxrequire(ucs_elem.text) or "0")
45
+ checksum_result = ChecksumResult.from_et(
46
+ odxrequire(et_element.find("CHECKSUM-RESULT")), context)
47
+
48
+ return Checksum(
49
+ fillbyte=fillbyte,
50
+ source_start_address=source_start_address,
51
+ compressed_size=compressed_size,
52
+ checksum_alg=checksum_alg,
53
+ source_end_address=source_end_address,
54
+ uncompressed_size=uncompressed_size,
55
+ checksum_result=checksum_result,
56
+ **kwargs)
57
+
58
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
59
+ odxlinks = {self.odx_id: self}
60
+
61
+ return odxlinks
62
+
63
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
64
+ pass
65
+
66
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
67
+ pass
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from .validityfor import ValidityFor
3
+
4
+ # Note that the ODX specification specifies a separate tag for this,
5
+ # but this tag is identical to VALIDITY-FOR, so let's use a type alias
6
+ # to reduce the amount of copy-and-pasted code
7
+ ChecksumResult = ValidityFor
odxtools/cli/compare.py CHANGED
@@ -3,7 +3,8 @@
3
3
 
4
4
  import argparse
5
5
  import os
6
- from typing import Any, cast
6
+ from dataclasses import dataclass, field
7
+ from typing import Any
7
8
 
8
9
  from rich import print as rich_print
9
10
  from rich.padding import Padding as RichPadding
@@ -27,134 +28,134 @@ from ._print_utils import (extract_service_tabulation_data, print_dl_metrics,
27
28
  # name of the tool
28
29
  _odxtools_tool_name_ = "compare"
29
30
 
30
- VariantName = str
31
- VariantType = str
32
- NewServices = list[DiagService]
33
- DeletedServices = list[DiagService]
34
- RenamedServices = list[list[str | DiagService]]
35
- ServicesWithParamChanges = list[list[str | DiagService]]
36
31
 
37
- SpecsServiceDict = dict[str, VariantName | VariantType | NewServices | DeletedServices
38
- | RenamedServices | ServicesWithParamChanges]
32
+ @dataclass
33
+ class ChangedParameterDetails:
34
+ service: DiagService # The service whose parameters changed
35
+ changed_parameters: list[DiagService] = field(
36
+ default_factory=list) # list of changed parameter names
37
+ change_details: list[DiagService] = field(default_factory=list) # Detailed change information
39
38
 
40
- NewVariants = list[DiagLayer]
41
- DeletedVariants = list[DiagLayer]
42
39
 
43
- SpecsChangesVariants = dict[str, NewVariants | DeletedVariants | SpecsServiceDict]
40
+ @dataclass
41
+ class ServiceDiff:
42
+ diag_layer: str
43
+ diag_layer_type: str
44
+ new_services: list[DiagService] = field(default_factory=list)
45
+ deleted_services: list[DiagService] = field(default_factory=list)
46
+ changed_name_of_service: list[list[str | DiagService]] = field(default_factory=list)
47
+ changed_parameters_of_service: list[ChangedParameterDetails] = field(default_factory=list)
48
+
49
+
50
+ @dataclass
51
+ class SpecsChangesVariants:
52
+ new_diagnostic_layers: list[DiagLayer] = field(default_factory=list)
53
+ deleted_diagnostic_layers: list[DiagLayer] = field(default_factory=list)
54
+ service_changes: dict[str, list[DiagLayer] | ServiceDiff] = field(default_factory=dict)
44
55
 
45
56
 
46
57
  class Display:
47
- # class with variables and functions to display the result of the comparison
48
58
 
49
- # TODO
50
- # - Idea: results as json export
51
- # - write results of comparison in json structure
52
- # - use odxlinks to refer to dignostic services / objects if
53
- # changes have already been detected (e.g. in another ecu
54
- # variant / diagnostic layer)
55
- # - print all information about parameter properties (request,
56
- # pos. response & neg. response parameters) for changed diagnostic
57
- # services
58
59
  param_detailed: bool
59
60
  obj_detailed: bool
60
61
 
61
62
  def __init__(self) -> None:
62
63
  pass
63
64
 
64
- def print_dl_changes(self, service_dict: SpecsServiceDict) -> None:
65
-
66
- if service_dict["new_services"] or service_dict["deleted_services"] or service_dict[
67
- "changed_name_of_service"][0] or service_dict["changed_parameters_of_service"][0]:
68
- assert isinstance(service_dict["diag_layer"], str)
65
+ def print_dl_changes(self, service_spec: ServiceDiff) -> None:
66
+ if service_spec.new_services or service_spec.deleted_services or service_spec.changed_name_of_service or service_spec.changed_parameters_of_service:
67
+ assert isinstance(service_spec.diag_layer, str)
69
68
  rich_print()
70
69
  rich_print(
71
- f"Changed diagnostic services for diagnostic layer '{service_dict['diag_layer']}' ({service_dict['diag_layer_type']}):"
70
+ f"Changed diagnostic services for diagnostic layer '{service_spec.diag_layer}' ({service_spec.diag_layer_type}):"
72
71
  )
73
- if service_dict["new_services"]:
74
- assert isinstance(service_dict["new_services"], list)
72
+ if service_spec.new_services:
73
+ assert isinstance(service_spec.new_services, list)
75
74
  rich_print()
76
75
  rich_print(" [blue]New services[/blue]")
77
- rich_print(extract_service_tabulation_data(
78
- service_dict["new_services"])) # type: ignore[arg-type]
79
- if service_dict["deleted_services"]:
80
- assert isinstance(service_dict["deleted_services"], list)
76
+ rich_print(extract_service_tabulation_data(service_spec.new_services))
77
+ if service_spec.deleted_services:
78
+ assert isinstance(service_spec.deleted_services, list)
81
79
  rich_print()
82
80
  rich_print(" [blue]Deleted services[/blue]")
83
- rich_print(extract_service_tabulation_data(
84
- service_dict["deleted_services"])) # type: ignore[arg-type]
85
- if service_dict["changed_name_of_service"][0]:
81
+ rich_print(extract_service_tabulation_data(service_spec.deleted_services))
82
+ if service_spec.changed_name_of_service[0]:
86
83
  rich_print()
87
84
  rich_print(" [blue]Renamed services[/blue]")
88
- rich_print(extract_service_tabulation_data(
89
- service_dict["changed_name_of_service"][0])) # type: ignore[arg-type]
90
- if service_dict["changed_parameters_of_service"][0]:
91
- rich_print()
92
- rich_print(" [blue]Services with parameter changes[/blue]")
93
- # create table with information about services with parameter changes
94
- changed_param_column = [
95
- str(x) for x in service_dict["changed_parameters_of_service"][
96
- 1] # type: ignore[union-attr]
97
- ]
98
- table = extract_service_tabulation_data(
99
- service_dict["changed_parameters_of_service"][0], # type: ignore[arg-type]
100
- additional_columns=[("Changed Parameters", changed_param_column)])
101
- rich_print(table)
102
-
103
- for service_idx, service in enumerate(
104
- service_dict["changed_parameters_of_service"][0]): # type: ignore[arg-type]
105
- assert isinstance(service, DiagService)
85
+ tmp: list[DiagService] = []
86
+ for sublist in service_spec.changed_name_of_service:
87
+ for item in sublist:
88
+ if isinstance(item, DiagService):
89
+ tmp.append(item)
90
+ rich_print(extract_service_tabulation_data(tmp))
91
+ if service_spec.changed_parameters_of_service:
92
+ first_change_details = service_spec.changed_parameters_of_service[0]
93
+ if first_change_details:
106
94
  rich_print()
107
- rich_print(
108
- f" Detailed changes of diagnostic service [u cyan]{service.short_name}[/u cyan]"
109
- )
110
- # detailed_info in [infotext1, dict1, infotext2, dict2, ...]
111
- info_list = cast(
112
- list, # type: ignore[type-arg]
113
- service_dict["changed_parameters_of_service"])[2][service_idx]
114
- for detailed_info in info_list:
115
- if isinstance(detailed_info, str):
116
- rich_print()
117
- rich_print(detailed_info)
118
- elif isinstance(detailed_info, dict):
119
- table = RichTable(
120
- show_header=True,
121
- header_style="bold cyan",
122
- border_style="blue",
123
- show_lines=True)
124
- for header in detailed_info:
125
- table.add_column(header)
126
- rows = zip(*detailed_info.values(), strict=False)
127
- for row in rows:
128
- table.add_row(*map(str, row))
129
-
130
- rich_print(RichPadding(table, pad=(0, 0, 0, 4)))
131
- rich_print()
132
- if self.param_detailed:
133
- # print all parameter details of diagnostic service
134
- print_service_parameters(service, allow_unknown_bit_lengths=True)
95
+ rich_print(" [blue]Services with parameter changes[/blue]")
96
+ changed_param_column = [
97
+ str(param_details.changed_parameters)
98
+ for param_details in service_spec.changed_parameters_of_service
99
+ ]
100
+ services = [
101
+ param_detail.service
102
+ for param_detail in service_spec.changed_parameters_of_service
103
+ ]
104
+ table = extract_service_tabulation_data(
105
+ services, additional_columns=[("Changed Parameters", changed_param_column)])
106
+ rich_print(table)
107
+ for service_idx, param_detail in enumerate(
108
+ service_spec.changed_parameters_of_service):
109
+ service = param_detail.service
110
+
111
+ assert isinstance(service, DiagService)
112
+ rich_print()
113
+ rich_print(
114
+ f" Detailed changes of diagnostic service [u cyan]{service.short_name}[/u cyan]"
115
+ )
116
+
117
+ info_list = service_spec.changed_parameters_of_service[
118
+ service_idx].change_details
119
+ for detailed_info in info_list:
120
+ if isinstance(detailed_info, str):
121
+ rich_print()
122
+ rich_print(detailed_info)
123
+ elif isinstance(detailed_info, dict):
124
+ table = RichTable(
125
+ show_header=True,
126
+ header_style="bold cyan",
127
+ border_style="blue",
128
+ show_lines=True)
129
+ for header in detailed_info:
130
+ table.add_column(header)
131
+ rows = zip(*detailed_info.values(), strict=True)
132
+ for row in rows:
133
+ table.add_row(*map(str, row))
134
+
135
+ rich_print(RichPadding(table, pad=(0, 0, 0, 4)))
136
+ rich_print()
137
+ if self.param_detailed:
138
+ print_service_parameters(service, allow_unknown_bit_lengths=True)
135
139
 
136
140
  def print_database_changes(self, changes_variants: SpecsChangesVariants) -> None:
137
- # prints result of database comparison (input variable: dictionary: changes_variants)
138
141
 
139
- # diagnostic layers
140
- if changes_variants["new_diagnostic_layers"] or changes_variants[
141
- "deleted_diagnostic_layers"]:
142
+ if changes_variants.new_diagnostic_layers or changes_variants.deleted_diagnostic_layers:
142
143
  rich_print()
143
144
  rich_print("[bright_blue]Changed diagnostic layers[/bright_blue]: ")
144
145
  rich_print(" New diagnostic layers: ")
145
- for variant in changes_variants["new_diagnostic_layers"]:
146
+ for variant in changes_variants.new_diagnostic_layers:
146
147
  assert isinstance(variant, DiagLayer)
147
148
  rich_print(
148
149
  f" [magenta]{variant.short_name}[/magenta] ({variant.variant_type.value})")
149
150
  rich_print(" Deleted diagnostic layers: ")
150
- for variant in changes_variants["deleted_diagnostic_layers"]:
151
+ for variant in changes_variants.deleted_diagnostic_layers:
151
152
  assert isinstance(variant, DiagLayer)
152
153
  rich_print(
153
154
  f" [magenta]{variant.short_name}[/magenta] ({variant.variant_type.value})")
154
155
 
155
156
  # diagnostic services
156
- for _, value in changes_variants.items():
157
- if isinstance(value, dict):
157
+ for _, value in changes_variants.service_changes.items():
158
+ if isinstance(value, ServiceDiff):
158
159
  self.print_dl_changes(value)
159
160
 
160
161
 
@@ -276,8 +277,7 @@ class Comparison(Display):
276
277
 
277
278
  return {"Property": property, "Old Value": old, "New Value": new}
278
279
 
279
- def compare_services(self, service1: DiagService,
280
- service2: DiagService) -> list[SpecsServiceDict]:
280
+ def compare_services(self, service1: DiagService, service2: DiagService) -> list[DiagService]:
281
281
  # compares request, positive response and negative response parameters of two diagnostic services
282
282
 
283
283
  information: list[str | dict[str, Any]] = [
@@ -405,39 +405,26 @@ class Comparison(Display):
405
405
 
406
406
  return [information, changed_params] # type: ignore[list-item]
407
407
 
408
- def compare_diagnostic_layers(self, dl1: DiagLayer,
409
- dl2: DiagLayer) -> dict: # type: ignore[type-arg]
408
+ def compare_diagnostic_layers(self, dl1: DiagLayer, dl2: DiagLayer) -> ServiceDiff:
410
409
  # compares diagnostic services of two diagnostic layers with each other
411
410
  # save changes in dictionary (service_dict)
412
411
  # TODO: add comparison of SingleECUJobs
413
412
 
414
- new_services: NewServices = []
415
- deleted_services: DeletedServices = []
416
- renamed_service: RenamedServices = [[],
417
- []] # TODO: implement list of (str, DiagService)-tuples
418
- services_with_param_changes: ServicesWithParamChanges = [
419
- [], [], []
420
- ] # TODO: implement list of tuples (str, str, DiagService)-tuples
421
-
422
- service_dict: SpecsServiceDict = {
423
- "diag_layer": dl1.short_name,
424
- "diag_layer_type": dl1.variant_type.value,
425
- # list with added diagnostic services [service1, service2, service3, ...] Type: DiagService
426
- "new_services": new_services,
427
- # list with deleted diagnostic services [service1, service2, service3, ...] Type: DiagService
428
- "deleted_services": deleted_services,
429
- # list with diagnostic services where the service name changed [[services], [old service names]]
430
- "changed_name_of_service": renamed_service,
431
- # list with diagnostic services where the service parameter changed [[services], [changed_parameters], [information_texts]]
432
- "changed_parameters_of_service": services_with_param_changes
433
- }
434
- # service_dict["changed_name_of_service"][{0 = services, 1 = old service names}][i]
435
- # service_dict["changed_parameters_of_service"][{0 = services, 1 = changed_parameters, 2 = information_texts}][i]
436
-
413
+ new_services: list[DiagService] = []
414
+ deleted_services: list[DiagService] = []
415
+ renamed_service: list[list[str | DiagService]] = [[], []] # list of (old_name, new_name)
416
+ services_with_param_changes: list[ChangedParameterDetails] = [
417
+ ] # Parameter changes # TODO: implement list of tuples (str, str, DiagService)-tuples
418
+
419
+ service_spec = ServiceDiff(
420
+ diag_layer=dl1.short_name,
421
+ diag_layer_type=dl1.variant_type.value,
422
+ new_services=new_services,
423
+ deleted_services=deleted_services,
424
+ changed_name_of_service=renamed_service,
425
+ changed_parameters_of_service=services_with_param_changes)
437
426
  dl1_service_names = [service.short_name for service in dl1.services]
438
427
 
439
- # extract the constant prefixes for the requests of all
440
- # services (used for duck-typed rename detection)
441
428
  dl1_request_prefixes: list[bytes | None] = [
442
429
  None if s.request is None else s.request.coded_const_prefix() for s in dl1.services
443
430
  ]
@@ -449,7 +436,7 @@ class Comparison(Display):
449
436
  for service1 in dl1.services:
450
437
 
451
438
  # check for added diagnostic services
452
- rq_prefix: bytes | None = None
439
+ rq_prefix: bytes
453
440
  if service1.request is not None:
454
441
  rq_prefix = service1.request.coded_const_prefix()
455
442
 
@@ -457,11 +444,11 @@ class Comparison(Display):
457
444
  if rq_prefix is None or rq_prefix not in dl2_request_prefixes:
458
445
  # TODO: this will not work in cases where the constant
459
446
  # prefix of a request was modified...
460
- service_dict["new_services"].append( # type: ignore[union-attr]
461
- service1) # type: ignore[arg-type]
462
447
 
448
+ service_spec.new_services.append(service1)
463
449
  # check whether names of diagnostic services have changed
464
450
  elif service1 not in dl2.services:
451
+
465
452
  if rq_prefix is None or rq_prefix in dl2_request_prefixes:
466
453
  # get related diagnostic service for request
467
454
  service2_idx = dl2_request_prefixes.index(rq_prefix)
@@ -470,30 +457,23 @@ class Comparison(Display):
470
457
  # save information about changes in dictionary
471
458
 
472
459
  # add new service (type: DiagService)
473
- service_dict["changed_name_of_service"][0].append( # type: ignore[union-attr]
474
- service1)
460
+
461
+ service_spec.changed_name_of_service[0].append(service1)
475
462
  # add old service name (type: String)
476
- service_dict["changed_name_of_service"][1].append( # type: ignore[union-attr]
477
- service2.short_name)
463
+ service_spec.changed_name_of_service[1].append(service2.short_name)
478
464
 
479
465
  # compare request, pos. response and neg. response parameters of diagnostic services
480
466
  detailed_information = self.compare_services(service1, service2)
481
- # detailed_information = [[infotext1, table1, infotext2, table2, ...], changed_params]
482
467
 
483
468
  # add information about changed diagnostic service parameters to dicitionary
484
469
  if detailed_information[1]: # check whether string "changed_params" is empty
485
- # new service (type: DiagService)
486
- service_dict["changed_parameters_of_service"][
487
- 0].append( # type: ignore[union-attr]
488
- service1)
489
- # add parameters which have been changed (type: String)
490
- service_dict["changed_parameters_of_service"][
491
- 1].append( # type: ignore[union-attr]
492
- detailed_information[1]) # type: ignore[arg-type]
493
- # add detailed information about changed service parameters (type: list) [infotext1, table1, infotext2, table2, ...]
494
- service_dict["changed_parameters_of_service"][
495
- 2].append( # type: ignore[union-attr]
496
- detailed_information[0]) # type: ignore[arg-type]
470
+ param_change_details = ChangedParameterDetails(
471
+ service=service1,
472
+ changed_parameters=[detailed_information[1]],
473
+ change_details=[detailed_information[0]],
474
+ )
475
+
476
+ service_spec.changed_parameters_of_service.append(param_change_details)
497
477
 
498
478
  for service2_idx, service2 in enumerate(dl2.services):
499
479
 
@@ -501,65 +481,58 @@ class Comparison(Display):
501
481
  if service2.short_name not in dl1_service_names and dl2_request_prefixes[
502
482
  service2_idx] not in dl1_request_prefixes:
503
483
 
504
- deleted_list = service_dict["deleted_services"]
484
+ deleted_list = service_spec.deleted_services
505
485
  assert isinstance(deleted_list, list)
506
486
  if service2 not in deleted_list:
507
- service_dict["deleted_services"].append( # type: ignore[union-attr]
508
- service2) # type: ignore[arg-type]
487
+ service_spec.deleted_services.append(service2)
509
488
 
510
489
  if service1.short_name == service2.short_name:
511
490
  # compare request, pos. response and neg. response parameters of both diagnostic services
512
491
  detailed_information = self.compare_services(service1, service2)
513
- # detailed_information = [[infotext1, table1, infotext2, table2, ...], changed_params]
514
492
 
515
493
  # add information about changed diagnostic service parameters to dicitionary
516
494
  if detailed_information[1]: # check whether string "changed_params" is empty
517
- # new service (type: DiagService)
518
- service_dict["changed_parameters_of_service"][
519
- 0].append( # type: ignore[union-attr]
520
- service1)
521
- # add parameters which have been changed (type: String)
522
- service_dict["changed_parameters_of_service"][ # type: ignore[union-attr]
523
- 1].append(detailed_information[1]) # type: ignore[arg-type]
524
- # add detailed information about changed service parameters (type: list) [infotext1, table1, infotext2, table2, ...]
525
- service_dict["changed_parameters_of_service"][ # type: ignore[union-attr]
526
- 2].append(detailed_information[0]) # type: ignore[arg-type]
527
- return service_dict
495
+ param_change_details = ChangedParameterDetails(
496
+ service=service1,
497
+ changed_parameters=[detailed_information[1]],
498
+ change_details=[detailed_information[0]],
499
+ )
500
+ service_spec.changed_parameters_of_service.append(param_change_details)
501
+
502
+ return service_spec
528
503
 
529
504
  def compare_databases(self, database_new: Database,
530
- database_old: Database) -> dict: # type: ignore[type-arg]
505
+ database_old: Database) -> SpecsChangesVariants:
531
506
  # compares two PDX-files with each other
532
507
 
533
- new_variants: NewVariants = []
534
- deleted_variants: DeletedVariants = []
508
+ new_variants: list[DiagLayer] = [] # Assuming it stores diagnostic layer names
509
+ deleted_variants: list[DiagLayer] = []
535
510
 
536
- changes_variants: SpecsChangesVariants = {
537
- "new_diagnostic_layers": new_variants,
538
- "deleted_diagnostic_layers": deleted_variants
539
- }
511
+ changes_variants = SpecsChangesVariants(
512
+ new_diagnostic_layers=new_variants,
513
+ deleted_diagnostic_layers=deleted_variants,
514
+ service_changes={})
540
515
 
541
516
  # compare databases
542
517
  for _, dl1 in enumerate(database_new.diag_layers):
543
518
  # check for new diagnostic layers
544
519
  if dl1.short_name not in [dl.short_name for dl in database_old.diag_layers]:
545
- changes_variants["new_diagnostic_layers"].append(dl1) # type: ignore[union-attr]
520
+ changes_variants.new_diagnostic_layers.append(dl1)
546
521
 
547
522
  for _, dl2 in enumerate(database_old.diag_layers):
548
523
  # check for deleted diagnostic layers
549
524
  if (dl2.short_name not in [dl.short_name for dl in database_new.diag_layers] and
550
- dl2 not in changes_variants["deleted_diagnostic_layers"]):
525
+ dl2 not in changes_variants.deleted_diagnostic_layers):
551
526
 
552
- changes_variants[
553
- "deleted_diagnostic_layers"].append( # type: ignore[union-attr]
554
- dl2)
527
+ changes_variants.deleted_diagnostic_layers.append(dl2)
555
528
 
556
529
  if dl1.short_name == dl2.short_name and dl1.short_name in self.diagnostic_layer_names:
557
530
  # compare diagnostic services of both diagnostic layers
558
531
  # save diagnostic service changes in dictionary (empty if no changes)
559
- service_dict: SpecsServiceDict = self.compare_diagnostic_layers(dl1, dl2)
560
- if service_dict:
532
+ service_spec: ServiceDiff = self.compare_diagnostic_layers(dl1, dl2)
533
+ if changes_variants.service_changes is not None:
561
534
  # adds information about diagnostic service changes to return variable (changes_variants)
562
- changes_variants.update({dl1.short_name: service_dict})
535
+ changes_variants.service_changes.update({dl1.short_name: service_spec})
563
536
 
564
537
  return changes_variants
565
538