odxtools 5.3.1__py3-none-any.whl → 6.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odxtools/__init__.py +1 -1
- odxtools/basicstructure.py +76 -91
- odxtools/cli/_parser_utils.py +12 -9
- odxtools/cli/_print_utils.py +7 -7
- odxtools/cli/browse.py +94 -73
- odxtools/cli/find.py +42 -59
- odxtools/cli/list.py +21 -17
- odxtools/cli/snoop.py +19 -18
- odxtools/communicationparameterref.py +6 -3
- odxtools/companydocinfo.py +2 -2
- odxtools/companyrevisioninfo.py +1 -1
- odxtools/comparamsubset.py +6 -6
- odxtools/complexcomparam.py +1 -1
- odxtools/compumethods/compumethod.py +6 -9
- odxtools/compumethods/createanycompumethod.py +11 -9
- odxtools/compumethods/identicalcompumethod.py +5 -4
- odxtools/compumethods/limit.py +9 -9
- odxtools/compumethods/linearcompumethod.py +25 -17
- odxtools/compumethods/scalelinearcompumethod.py +6 -5
- odxtools/compumethods/tabintpcompumethod.py +30 -9
- odxtools/compumethods/texttablecompumethod.py +22 -24
- odxtools/database.py +5 -5
- odxtools/dataobjectproperty.py +10 -23
- odxtools/decodestate.py +1 -1
- odxtools/determinenumberofitems.py +37 -8
- odxtools/diagcodedtype.py +14 -9
- odxtools/diagdatadictionaryspec.py +60 -37
- odxtools/diaglayer.py +30 -21
- odxtools/diaglayercontainer.py +40 -40
- odxtools/diaglayerraw.py +92 -63
- odxtools/diagnostictroublecode.py +2 -2
- odxtools/diagservice.py +53 -35
- odxtools/docrevision.py +1 -1
- odxtools/dopbase.py +14 -3
- odxtools/dtcdop.py +15 -9
- odxtools/dynamiclengthfield.py +6 -4
- odxtools/endofpdufield.py +22 -23
- odxtools/environmentdata.py +2 -5
- odxtools/environmentdatadescription.py +6 -4
- odxtools/field.py +3 -8
- odxtools/isotp_state_machine.py +52 -38
- odxtools/leadinglengthinfotype.py +9 -7
- odxtools/load_file.py +2 -1
- odxtools/load_odx_d_file.py +2 -5
- odxtools/load_pdx_file.py +2 -6
- odxtools/message.py +11 -3
- odxtools/minmaxlengthtype.py +107 -78
- odxtools/modification.py +2 -2
- odxtools/multiplexer.py +23 -21
- odxtools/multiplexerswitchkey.py +37 -8
- odxtools/nameditemlist.py +59 -58
- odxtools/odxlink.py +4 -2
- odxtools/odxtypes.py +4 -3
- odxtools/parameterinfo.py +6 -6
- odxtools/parameters/codedconstparameter.py +15 -25
- odxtools/parameters/createanyparameter.py +1 -1
- odxtools/parameters/dynamicparameter.py +6 -5
- odxtools/parameters/lengthkeyparameter.py +2 -1
- odxtools/parameters/matchingrequestparameter.py +8 -11
- odxtools/parameters/nrcconstparameter.py +11 -21
- odxtools/parameters/parameter.py +4 -18
- odxtools/parameters/parameterwithdop.py +14 -29
- odxtools/parameters/physicalconstantparameter.py +7 -9
- odxtools/parameters/reservedparameter.py +17 -38
- odxtools/parameters/systemparameter.py +6 -5
- odxtools/parameters/tableentryparameter.py +6 -5
- odxtools/parameters/tablekeyparameter.py +8 -15
- odxtools/parameters/tablestructparameter.py +11 -12
- odxtools/parameters/valueparameter.py +9 -24
- odxtools/paramlengthinfotype.py +11 -9
- odxtools/physicaldimension.py +1 -1
- odxtools/physicaltype.py +2 -2
- odxtools/response.py +7 -3
- odxtools/singleecujob.py +48 -22
- odxtools/standardlengthtype.py +11 -6
- odxtools/uds.py +1 -1
- odxtools/unit.py +5 -5
- odxtools/unitgroup.py +1 -1
- odxtools/unitspec.py +2 -2
- odxtools/version.py +13 -3
- odxtools/write_pdx_file.py +7 -4
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/METADATA +7 -5
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/RECORD +87 -88
- odxtools/positioneddataobjectproperty.py +0 -74
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/LICENSE +0 -0
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/WHEEL +0 -0
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/entry_points.txt +0 -0
- {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/top_level.txt +0 -0
odxtools/minmaxlengthtype.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import Optional
|
3
|
+
from typing import Optional, Tuple
|
4
4
|
|
5
5
|
from .decodestate import DecodeState
|
6
6
|
from .diagcodedtype import DctType, DiagCodedType
|
7
7
|
from .encodestate import EncodeState
|
8
|
-
from .exceptions import DecodeError, EncodeError, odxassert
|
9
|
-
from .odxtypes import DataType
|
8
|
+
from .exceptions import DecodeError, EncodeError, odxassert, odxraise
|
9
|
+
from .odxtypes import AtomicOdxType, DataType
|
10
10
|
|
11
11
|
|
12
12
|
@dataclass
|
@@ -15,7 +15,7 @@ class MinMaxLengthType(DiagCodedType):
|
|
15
15
|
max_length: Optional[int]
|
16
16
|
termination: str
|
17
17
|
|
18
|
-
def __post_init__(self):
|
18
|
+
def __post_init__(self) -> None:
|
19
19
|
odxassert(self.max_length is None or self.min_length <= self.max_length)
|
20
20
|
odxassert(
|
21
21
|
self.base_data_type in [
|
@@ -34,113 +34,142 @@ class MinMaxLengthType(DiagCodedType):
|
|
34
34
|
def dct_type(self) -> DctType:
|
35
35
|
return "MIN-MAX-LENGTH-TYPE"
|
36
36
|
|
37
|
-
def
|
38
|
-
"""Returns the termination
|
39
|
-
# The termination
|
37
|
+
def __termination_sequence(self) -> bytes:
|
38
|
+
"""Returns the termination byte sequence if it isn't defined."""
|
39
|
+
# The termination sequence is actually not specified by ASAM
|
40
40
|
# for A_BYTEFIELD but I assume it is only one byte.
|
41
|
-
|
41
|
+
termination_sequence = b''
|
42
42
|
if self.termination == "ZERO":
|
43
43
|
if self.base_data_type not in [DataType.A_UNICODE2STRING]:
|
44
|
-
|
44
|
+
termination_sequence = bytes([0x0])
|
45
45
|
else:
|
46
|
-
|
46
|
+
termination_sequence = bytes([0x0, 0x0])
|
47
47
|
elif self.termination == "HEX-FF":
|
48
48
|
if self.base_data_type not in [DataType.A_UNICODE2STRING]:
|
49
|
-
|
49
|
+
termination_sequence = bytes([0xFF])
|
50
50
|
else:
|
51
|
-
|
52
|
-
return
|
51
|
+
termination_sequence = bytes([0xFF, 0xFF])
|
52
|
+
return termination_sequence
|
53
53
|
|
54
|
-
def convert_internal_to_bytes(self, internal_value, encode_state: EncodeState,
|
54
|
+
def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
|
55
55
|
bit_position: int) -> bytes:
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# The coded value must not have a length greater than max_length
|
63
|
-
if self.max_length and byte_length > self.max_length:
|
64
|
-
raise EncodeError(f"The internal value {internal_value} requires {byte_length}"
|
65
|
-
f" bytes, but the max length is {self.max_length}")
|
66
|
-
|
67
|
-
value_byte = self._to_bytes(
|
68
|
-
internal_value,
|
69
|
-
bit_position=0,
|
70
|
-
bit_length=8 * byte_length,
|
71
|
-
base_data_type=self.base_data_type,
|
72
|
-
is_highlow_byte_order=self.is_highlow_byte_order,
|
73
|
-
)
|
74
|
-
|
75
|
-
if encode_state.is_end_of_pdu or byte_length == self.max_length:
|
76
|
-
# All termination types may be ended by the PDU
|
77
|
-
return value_byte
|
56
|
+
if not isinstance(internal_value, (bytes, str)):
|
57
|
+
odxraise("MinMaxLengthType is currently only implemented for strings and byte arrays",
|
58
|
+
EncodeError)
|
59
|
+
|
60
|
+
if self.max_length is not None:
|
61
|
+
data_length = min(len(internal_value), self.max_length)
|
78
62
|
else:
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
63
|
+
data_length = len(internal_value)
|
64
|
+
|
65
|
+
value_bytes = bytearray(
|
66
|
+
self._to_bytes(
|
67
|
+
internal_value,
|
68
|
+
bit_position=0,
|
69
|
+
bit_length=8 * data_length,
|
70
|
+
base_data_type=self.base_data_type,
|
71
|
+
is_highlow_byte_order=self.is_highlow_byte_order,
|
72
|
+
))
|
73
|
+
|
74
|
+
# TODO: ensure that the termination delimiter is not
|
75
|
+
# encountered within the encoded value.
|
88
76
|
|
89
|
-
|
90
|
-
if
|
91
|
-
|
77
|
+
odxassert(self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu)
|
78
|
+
if encode_state.is_end_of_pdu or len(value_bytes) == self.max_length:
|
79
|
+
# All termination types may be ended by the end of the PDU
|
80
|
+
# or once reaching the maximum length. In this case, we
|
81
|
+
# must not add the termination sequence
|
82
|
+
pass
|
83
|
+
else:
|
84
|
+
termination_sequence = self.__termination_sequence()
|
85
|
+
|
86
|
+
# ensure that we don't try to encode an odd-length
|
87
|
+
# value when using a two-byte terminator
|
88
|
+
odxassert(len(value_bytes) % len(termination_sequence) == 0)
|
89
|
+
|
90
|
+
value_bytes.extend(termination_sequence)
|
91
|
+
|
92
|
+
if len(value_bytes) < self.min_length:
|
93
|
+
raise EncodeError(f"Encoded value for MinMaxLengthType "
|
94
|
+
f"must be at least {self.min_length} bytes long. "
|
95
|
+
f"(Is: {len(value_bytes)} bytes.)")
|
96
|
+
elif self.max_length is not None and len(value_bytes) > self.max_length:
|
97
|
+
raise EncodeError(f"Encoded value for MinMaxLengthType "
|
98
|
+
f"must not be longer than {self.max_length} bytes. "
|
99
|
+
f"(Is: {len(value_bytes)} bytes.)")
|
100
|
+
|
101
|
+
return value_bytes
|
102
|
+
|
103
|
+
def convert_bytes_to_internal(self,
|
104
|
+
decode_state: DecodeState,
|
105
|
+
bit_position: int = 0) -> Tuple[AtomicOdxType, int]:
|
106
|
+
if decode_state.cursor_position + self.min_length > len(decode_state.coded_message):
|
107
|
+
raise DecodeError("The PDU ended before minimum length was reached.")
|
92
108
|
|
93
109
|
coded_message = decode_state.coded_message
|
94
|
-
|
95
|
-
|
110
|
+
cursor_pos = decode_state.cursor_position
|
111
|
+
termination_seq = self.__termination_sequence()
|
96
112
|
|
97
|
-
|
98
|
-
max_termination_byte = len(coded_message)
|
113
|
+
max_terminator_pos = len(coded_message)
|
99
114
|
if self.max_length is not None:
|
100
|
-
|
115
|
+
max_terminator_pos = min(max_terminator_pos, cursor_pos + self.max_length)
|
101
116
|
|
102
117
|
if self.termination != "END-OF-PDU":
|
103
|
-
# The parameter either ends after
|
104
|
-
# or if a termination
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
+
# The parameter either ends after the maximum length, at
|
119
|
+
# the end of the PDU or if a termination sequence is
|
120
|
+
# found.
|
121
|
+
|
122
|
+
terminator_pos = cursor_pos + self.min_length
|
123
|
+
while True:
|
124
|
+
# Search the termination sequence
|
125
|
+
terminator_pos = coded_message.find(termination_seq, terminator_pos,
|
126
|
+
max_terminator_pos)
|
127
|
+
if terminator_pos < 0:
|
128
|
+
# termination sequence was not found, i.e., we
|
129
|
+
# are terminated by either the end of the PDU or
|
130
|
+
# our maximum size. (whatever is the smaller
|
131
|
+
# value.)
|
132
|
+
byte_length = max_terminator_pos - cursor_pos
|
133
|
+
break
|
134
|
+
elif (terminator_pos - cursor_pos) % len(termination_seq) == 0:
|
135
|
+
# we found the termination sequence at a position
|
136
|
+
# and it is correctly aligned (two-byte
|
137
|
+
# termination sequences must be word aligned
|
138
|
+
# relative to the beginning of the parameter)!
|
139
|
+
byte_length = terminator_pos - cursor_pos
|
140
|
+
break
|
141
|
+
else:
|
142
|
+
# we found the termination sequence, but its
|
143
|
+
# alignment was incorrect. Try again one byte
|
144
|
+
# further...
|
145
|
+
terminator_pos += 1
|
118
146
|
|
119
147
|
# Extract the value
|
120
|
-
value,
|
148
|
+
value, byte_pos = self._extract_internal(
|
121
149
|
decode_state.coded_message,
|
122
|
-
byte_position=
|
150
|
+
byte_position=cursor_pos,
|
123
151
|
bit_position=bit_position,
|
124
152
|
bit_length=8 * byte_length,
|
125
153
|
base_data_type=self.base_data_type,
|
126
154
|
is_highlow_byte_order=self.is_highlow_byte_order,
|
127
155
|
)
|
128
|
-
odxassert(byte == termination_byte)
|
129
156
|
|
130
|
-
|
131
|
-
|
132
|
-
|
157
|
+
if byte_pos != len(coded_message) and byte_pos - cursor_pos != self.max_length:
|
158
|
+
byte_pos += len(termination_seq)
|
159
|
+
|
160
|
+
# next byte starts after the actual data and the termination sequence
|
161
|
+
return value, byte_pos
|
133
162
|
else:
|
134
163
|
# If termination == "END-OF-PDU", the parameter ends after max_length
|
135
164
|
# or at the end of the PDU.
|
136
|
-
byte_length =
|
165
|
+
byte_length = max_terminator_pos - cursor_pos
|
137
166
|
|
138
|
-
value,
|
167
|
+
value, byte_pos = self._extract_internal(
|
139
168
|
decode_state.coded_message,
|
140
|
-
byte_position=
|
169
|
+
byte_position=cursor_pos,
|
141
170
|
bit_position=bit_position,
|
142
171
|
bit_length=8 * byte_length,
|
143
172
|
base_data_type=self.base_data_type,
|
144
173
|
is_highlow_byte_order=self.is_highlow_byte_order,
|
145
174
|
)
|
146
|
-
return value,
|
175
|
+
return value, byte_pos
|
odxtools/modification.py
CHANGED
@@ -24,8 +24,8 @@ class Modification:
|
|
24
24
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
25
25
|
return {}
|
26
26
|
|
27
|
-
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase):
|
27
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
28
28
|
pass
|
29
29
|
|
30
|
-
def _resolve_snrefs(self, diag_layer: "DiagLayer"):
|
30
|
+
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
31
31
|
pass
|
odxtools/multiplexer.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
|
-
from collections import OrderedDict
|
3
2
|
from dataclasses import dataclass
|
4
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast
|
5
4
|
from xml.etree import ElementTree
|
6
5
|
|
7
6
|
from .createsdgs import create_sdgs_from_et
|
@@ -14,7 +13,7 @@ from .multiplexercase import MultiplexerCase
|
|
14
13
|
from .multiplexerdefaultcase import MultiplexerDefaultCase
|
15
14
|
from .multiplexerswitchkey import MultiplexerSwitchKey
|
16
15
|
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
17
|
-
from .odxtypes import odxstr_to_bool
|
16
|
+
from .odxtypes import AtomicOdxType, ParameterValue, odxstr_to_bool
|
18
17
|
from .utils import dataclass_fields_asdict
|
19
18
|
|
20
19
|
if TYPE_CHECKING:
|
@@ -58,17 +57,13 @@ class Multiplexer(DopBase):
|
|
58
57
|
cases=cases,
|
59
58
|
**kwargs)
|
60
59
|
|
61
|
-
|
62
|
-
def bit_length(self):
|
63
|
-
return None
|
64
|
-
|
65
|
-
def _get_case_limits(self, case: MultiplexerCase):
|
60
|
+
def _get_case_limits(self, case: MultiplexerCase) -> Tuple[AtomicOdxType, AtomicOdxType]:
|
66
61
|
key_type = self.switch_key.dop.physical_type.base_data_type
|
67
62
|
lower_limit = key_type.make_from(case.lower_limit)
|
68
63
|
upper_limit = key_type.make_from(case.upper_limit)
|
69
64
|
return lower_limit, upper_limit
|
70
65
|
|
71
|
-
def convert_physical_to_bytes(self, physical_value, encode_state: EncodeState,
|
66
|
+
def convert_physical_to_bytes(self, physical_value: ParameterValue, encode_state: EncodeState,
|
72
67
|
bit_position: int) -> bytes:
|
73
68
|
|
74
69
|
if bit_position != 0:
|
@@ -88,10 +83,11 @@ class Multiplexer(DopBase):
|
|
88
83
|
case_bytes = case._structure.convert_physical_to_bytes(
|
89
84
|
case_value, encode_state, 0)
|
90
85
|
else:
|
91
|
-
case_bytes =
|
86
|
+
case_bytes = b''
|
92
87
|
|
93
88
|
key_value, _ = self._get_case_limits(case)
|
94
|
-
key_bytes = self.switch_key.convert_physical_to_bytes(
|
89
|
+
key_bytes = self.switch_key.dop.convert_physical_to_bytes(
|
90
|
+
key_value, encode_state, bit_position=self.switch_key.bit_position or 0)
|
95
91
|
|
96
92
|
mux_len = max(len(key_bytes), len(case_bytes) + case_pos)
|
97
93
|
mux_bytes = bytearray(mux_len)
|
@@ -102,18 +98,20 @@ class Multiplexer(DopBase):
|
|
102
98
|
|
103
99
|
raise EncodeError(f"The case {case_name} is not found in Multiplexer {self.short_name}")
|
104
100
|
|
105
|
-
def convert_bytes_to_physical(self,
|
101
|
+
def convert_bytes_to_physical(self,
|
102
|
+
decode_state: DecodeState,
|
103
|
+
bit_position: int = 0) -> Tuple[ParameterValue, int]:
|
106
104
|
|
107
105
|
if bit_position != 0:
|
108
|
-
raise DecodeError("
|
106
|
+
raise DecodeError("Multiplexers must be byte-aligned, i.e. bit_position=0, but "
|
109
107
|
f"{self.short_name} was passed the bit position {bit_position}")
|
110
|
-
key_value, key_next_byte = self.switch_key.convert_bytes_to_physical(decode_state)
|
108
|
+
key_value, key_next_byte = self.switch_key.dop.convert_bytes_to_physical(decode_state)
|
111
109
|
|
112
|
-
byte_code = decode_state.coded_message[decode_state.
|
110
|
+
byte_code = decode_state.coded_message[decode_state.cursor_position:]
|
113
111
|
case_decode_state = DecodeState(
|
114
112
|
coded_message=byte_code[self.byte_position:],
|
115
|
-
parameter_values=
|
116
|
-
|
113
|
+
parameter_values={},
|
114
|
+
cursor_position=0,
|
117
115
|
)
|
118
116
|
case_found = False
|
119
117
|
case_next_byte = 0
|
@@ -135,16 +133,20 @@ class Multiplexer(DopBase):
|
|
135
133
|
|
136
134
|
if not case_found:
|
137
135
|
raise DecodeError(
|
138
|
-
f"Failed to find a matching case in {self.short_name} for value {key_value}")
|
136
|
+
f"Failed to find a matching case in {self.short_name} for value {key_value!r}")
|
139
137
|
|
140
|
-
mux_value =
|
141
|
-
mux_next_byte = decode_state.
|
138
|
+
mux_value = {case.short_name: cast(ParameterValue, case_value)}
|
139
|
+
mux_next_byte = decode_state.cursor_position + max(
|
142
140
|
key_next_byte + self.switch_key.byte_position, case_next_byte + self.byte_position)
|
143
141
|
return mux_value, mux_next_byte
|
144
142
|
|
145
143
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
146
144
|
odxlinks = super()._build_odxlinks()
|
147
145
|
|
146
|
+
odxlinks.update(self.switch_key._build_odxlinks())
|
147
|
+
if self.default_case is not None:
|
148
|
+
odxlinks.update(self.default_case._build_odxlinks())
|
149
|
+
|
148
150
|
return odxlinks
|
149
151
|
|
150
152
|
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
@@ -157,7 +159,7 @@ class Multiplexer(DopBase):
|
|
157
159
|
for case in self.cases:
|
158
160
|
case._resolve_odxlinks(odxlinks)
|
159
161
|
|
160
|
-
def _resolve_snrefs(self, diag_layer: "DiagLayer"):
|
162
|
+
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
161
163
|
super()._resolve_snrefs(diag_layer)
|
162
164
|
|
163
165
|
self.switch_key._resolve_snrefs(diag_layer)
|
odxtools/multiplexerswitchkey.py
CHANGED
@@ -1,18 +1,47 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
|
-
from typing import List
|
2
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
3
3
|
from xml.etree import ElementTree
|
4
4
|
|
5
|
-
from .
|
6
|
-
from .
|
7
|
-
from .
|
5
|
+
from .dataobjectproperty import DataObjectProperty
|
6
|
+
from .exceptions import odxrequire
|
7
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from ..diaglayer import DiagLayer
|
8
11
|
|
9
12
|
|
10
13
|
@dataclass
|
11
|
-
class MultiplexerSwitchKey
|
14
|
+
class MultiplexerSwitchKey:
|
15
|
+
"""
|
16
|
+
The object that determines the case to be used by a multiplexer
|
17
|
+
"""
|
18
|
+
byte_position: int
|
19
|
+
bit_position: Optional[int]
|
20
|
+
dop_ref: OdxLinkRef
|
12
21
|
|
13
22
|
@staticmethod
|
14
23
|
def from_et(et_element: ElementTree.Element,
|
15
24
|
doc_frags: List[OdxDocFragment]) -> "MultiplexerSwitchKey":
|
16
|
-
|
17
|
-
|
18
|
-
|
25
|
+
byte_position = int(odxrequire(et_element.findtext("BYTE-POSITION")))
|
26
|
+
bit_position_str = et_element.findtext("BIT-POSITION")
|
27
|
+
bit_position = int(bit_position_str) if bit_position_str is not None else None
|
28
|
+
dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), doc_frags))
|
29
|
+
|
30
|
+
return MultiplexerSwitchKey(
|
31
|
+
byte_position=byte_position,
|
32
|
+
bit_position=bit_position,
|
33
|
+
dop_ref=dop_ref,
|
34
|
+
)
|
35
|
+
|
36
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
37
|
+
return {}
|
38
|
+
|
39
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
40
|
+
self._dop = odxlinks.resolve(self.dop_ref, DataObjectProperty)
|
41
|
+
|
42
|
+
def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
|
43
|
+
pass
|
44
|
+
|
45
|
+
@property
|
46
|
+
def dop(self) -> DataObjectProperty:
|
47
|
+
return self._dop
|
odxtools/nameditemlist.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
import abc
|
3
|
+
from collections import OrderedDict
|
3
4
|
from keyword import iskeyword
|
4
|
-
from typing import (Callable, Collection, Generic, Iterable, List, Optional,
|
5
|
-
TypeVar, Union, cast, overload, runtime_checkable)
|
5
|
+
from typing import (Any, Callable, Collection, Dict, Generic, Iterable, Iterator, List, Optional,
|
6
|
+
Protocol, Tuple, TypeVar, Union, cast, overload, runtime_checkable)
|
6
7
|
|
7
8
|
from .exceptions import odxraise
|
8
9
|
|
@@ -19,7 +20,7 @@ T = TypeVar("T")
|
|
19
20
|
TNamed = TypeVar("TNamed", bound=OdxNamed)
|
20
21
|
|
21
22
|
|
22
|
-
class
|
23
|
+
class ItemAttributeList(Generic[T]):
|
23
24
|
"""A list that provides direct access to its items as named attributes.
|
24
25
|
|
25
26
|
This is a hybrid between a list and a user-defined object: One can
|
@@ -34,8 +35,8 @@ class ItemList(Generic[T]):
|
|
34
35
|
"""
|
35
36
|
|
36
37
|
def __init__(self, input_list: Optional[Iterable[T]] = None) -> None:
|
37
|
-
self.
|
38
|
-
self.
|
38
|
+
self._item_dict: OrderedDict[str, T] = OrderedDict()
|
39
|
+
self._item_list: List[T] = []
|
39
40
|
|
40
41
|
if input_list is not None:
|
41
42
|
for item in input_list:
|
@@ -54,53 +55,57 @@ class ItemList(Generic[T]):
|
|
54
55
|
"""
|
55
56
|
item_name = self._get_item_key(item)
|
56
57
|
|
57
|
-
# eliminate conflicts between the item
|
58
|
-
# attributes of the
|
58
|
+
# eliminate conflicts between the name of the new item and
|
59
|
+
# existing attributes of the ItemAttributeList object
|
59
60
|
i = 1
|
60
61
|
tmp = item_name
|
61
62
|
while True:
|
62
|
-
|
63
|
-
# might be slow, but NamedItemList is not meant for
|
64
|
-
# ginormous lists...
|
65
|
-
if tmp not in dir(self):
|
63
|
+
if not hasattr(self, tmp):
|
66
64
|
break
|
67
65
|
|
68
66
|
i += 1
|
69
67
|
if item_name.endswith("_"):
|
68
|
+
# if the item name already ends with an underscore,
|
69
|
+
# there's no need to add a second one...
|
70
70
|
tmp = f"{item_name}{i}"
|
71
71
|
else:
|
72
72
|
tmp = f"{item_name}_{i}"
|
73
|
+
|
73
74
|
item_name = tmp
|
74
75
|
|
75
|
-
self.
|
76
|
-
self.
|
77
|
-
self._values.append(item)
|
76
|
+
self._item_dict[item_name] = item
|
77
|
+
self._item_list.append(item)
|
78
78
|
|
79
79
|
def sort(self, key: Optional[Callable[[T], str]] = None, reverse: bool = False) -> None:
|
80
|
-
tmp = list(zip(self._names, self._values))
|
81
80
|
if key is None:
|
82
|
-
|
81
|
+
self._item_dict = OrderedDict(
|
82
|
+
sorted(self._item_dict.items(), key=lambda x: x[0], reverse=reverse))
|
83
83
|
else:
|
84
84
|
key_fn = cast(Callable[[T], str], key)
|
85
|
-
|
85
|
+
self._item_dict = OrderedDict(
|
86
|
+
sorted(self._item_dict.items(), key=lambda x: key_fn(x[1]), reverse=reverse))
|
86
87
|
|
87
|
-
self.
|
88
|
-
self._values = [x[1] for x in tmp]
|
88
|
+
self._item_list = list(self._item_dict.values())
|
89
89
|
|
90
90
|
def keys(self) -> Collection[str]:
|
91
|
-
return self.
|
91
|
+
return self._item_dict.keys()
|
92
92
|
|
93
93
|
def values(self) -> Collection[T]:
|
94
|
-
return self.
|
94
|
+
return self._item_list
|
95
95
|
|
96
|
-
def items(self) ->
|
97
|
-
return
|
96
|
+
def items(self) -> Collection[Tuple[str, T]]:
|
97
|
+
return self._item_dict.items()
|
98
98
|
|
99
99
|
def __contains__(self, x: T) -> bool:
|
100
|
-
return x in self.
|
100
|
+
return x in self._item_list
|
101
101
|
|
102
102
|
def __len__(self) -> int:
|
103
|
-
return len(self.
|
103
|
+
return len(self._item_list)
|
104
|
+
|
105
|
+
def __dir__(self) -> Dict[str, Any]:
|
106
|
+
result = dict(self.__dict__)
|
107
|
+
result.update(self._item_dict)
|
108
|
+
return result
|
104
109
|
|
105
110
|
@overload
|
106
111
|
def __getitem__(self, key: int) -> T:
|
@@ -116,59 +121,55 @@ class ItemList(Generic[T]):
|
|
116
121
|
|
117
122
|
def __getitem__(self, key: Union[int, str, slice]) -> Union[T, List[T]]:
|
118
123
|
if isinstance(key, int):
|
119
|
-
|
120
|
-
# we want to raise a KeyError instead of an IndexError
|
121
|
-
# if the index is out of range...
|
122
|
-
raise KeyError(f"Tried to access item {key} of a NamedItemList "
|
123
|
-
f"of length {len(self)}")
|
124
|
-
|
125
|
-
return self._values[key]
|
124
|
+
return self._item_list[key]
|
126
125
|
elif isinstance(key, slice):
|
127
|
-
|
128
|
-
# because if the key is a slice, we cannot return a single
|
129
|
-
# item. (alternatively, the return type of this method
|
130
|
-
# could be defined as Union[T, List[T]], but this leads
|
131
|
-
# mypy to produce *many* spurious and hard to fix errors.
|
132
|
-
return self._values[key]
|
126
|
+
return self._item_list[key]
|
133
127
|
else:
|
134
|
-
return self.
|
128
|
+
return self._item_dict[key]
|
129
|
+
|
130
|
+
def __getattr__(self, key: str) -> T:
|
131
|
+
if key not in self._item_dict:
|
132
|
+
raise AttributeError(f"ItemAttributeList does not contain an item named '{key}'")
|
133
|
+
|
134
|
+
return self._item_dict[key]
|
135
135
|
|
136
136
|
def get(self, key: Union[int, str], default: Optional[T] = None) -> Optional[T]:
|
137
137
|
if isinstance(key, int):
|
138
|
-
if abs(key) < -len(self.
|
138
|
+
if abs(key) < -len(self._item_dict) or key >= len(self._item_dict):
|
139
139
|
return default
|
140
140
|
|
141
|
-
return self.
|
141
|
+
return self._item_list[key]
|
142
142
|
else:
|
143
|
-
return cast(Optional[T], self.
|
143
|
+
return cast(Optional[T], self._item_dict.get(key, default))
|
144
144
|
|
145
145
|
def __eq__(self, other: object) -> bool:
|
146
146
|
"""
|
147
|
-
|
148
|
-
Note that this does not consider the map `item_to_name_fn`.
|
147
|
+
Item lists are equal if the underlying lists are equal.
|
149
148
|
"""
|
150
|
-
if not isinstance(other,
|
149
|
+
if not isinstance(other, type(self)) or not isinstance(self, type(other)):
|
151
150
|
return False
|
152
151
|
else:
|
153
|
-
return self.
|
152
|
+
return self._item_dict == other._item_dict
|
154
153
|
|
155
|
-
|
156
|
-
|
157
|
-
return iter(self._values)
|
154
|
+
def __iter__(self) -> Iterator[T]:
|
155
|
+
return iter(self._item_list)
|
158
156
|
|
159
157
|
def __str__(self) -> str:
|
160
|
-
return f"[{', '.join(
|
158
|
+
return f"[{', '.join(self._item_dict.keys())}]"
|
161
159
|
|
162
160
|
def __repr__(self) -> str:
|
163
161
|
return self.__str__()
|
164
162
|
|
165
163
|
|
166
164
|
def short_name_as_key(obj: OdxNamed) -> str:
|
167
|
-
"""
|
165
|
+
"""Transform an object's `short_name` attribute into a valid
|
166
|
+
python identifier
|
167
|
+
|
168
|
+
Although short names are almost identical to valid python
|
169
|
+
identifiers, their first character is allowed to be a number or
|
170
|
+
they may be python keywords. This method prepends an underscore to
|
171
|
+
such short names.
|
168
172
|
|
169
|
-
Although short names are almost identical to python identifiers,
|
170
|
-
their first character is allowed to be a number. This method
|
171
|
-
prepends an underscore to such shortnames.
|
172
173
|
"""
|
173
174
|
if not isinstance(obj, OdxNamed):
|
174
175
|
odxraise()
|
@@ -176,16 +177,16 @@ def short_name_as_key(obj: OdxNamed) -> str:
|
|
176
177
|
if not isinstance(sn, str):
|
177
178
|
odxraise()
|
178
179
|
|
179
|
-
# make sure that the name of the item in question
|
180
|
-
#
|
181
|
-
#
|
180
|
+
# make sure that the name of the item in question is not a python
|
181
|
+
# keyword (this would lead to syntax errors) and that does not
|
182
|
+
# start with a digit
|
182
183
|
if sn[0].isdigit() or iskeyword(sn):
|
183
184
|
return f"_{sn}"
|
184
185
|
|
185
186
|
return sn
|
186
187
|
|
187
188
|
|
188
|
-
class NamedItemList(Generic[TNamed],
|
189
|
+
class NamedItemList(Generic[TNamed], ItemAttributeList[TNamed]):
|
189
190
|
|
190
191
|
def _get_item_key(self, obj: OdxNamed) -> str:
|
191
192
|
return short_name_as_key(obj)
|