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.
Files changed (88) hide show
  1. odxtools/__init__.py +1 -1
  2. odxtools/basicstructure.py +76 -91
  3. odxtools/cli/_parser_utils.py +12 -9
  4. odxtools/cli/_print_utils.py +7 -7
  5. odxtools/cli/browse.py +94 -73
  6. odxtools/cli/find.py +42 -59
  7. odxtools/cli/list.py +21 -17
  8. odxtools/cli/snoop.py +19 -18
  9. odxtools/communicationparameterref.py +6 -3
  10. odxtools/companydocinfo.py +2 -2
  11. odxtools/companyrevisioninfo.py +1 -1
  12. odxtools/comparamsubset.py +6 -6
  13. odxtools/complexcomparam.py +1 -1
  14. odxtools/compumethods/compumethod.py +6 -9
  15. odxtools/compumethods/createanycompumethod.py +11 -9
  16. odxtools/compumethods/identicalcompumethod.py +5 -4
  17. odxtools/compumethods/limit.py +9 -9
  18. odxtools/compumethods/linearcompumethod.py +25 -17
  19. odxtools/compumethods/scalelinearcompumethod.py +6 -5
  20. odxtools/compumethods/tabintpcompumethod.py +30 -9
  21. odxtools/compumethods/texttablecompumethod.py +22 -24
  22. odxtools/database.py +5 -5
  23. odxtools/dataobjectproperty.py +10 -23
  24. odxtools/decodestate.py +1 -1
  25. odxtools/determinenumberofitems.py +37 -8
  26. odxtools/diagcodedtype.py +14 -9
  27. odxtools/diagdatadictionaryspec.py +60 -37
  28. odxtools/diaglayer.py +30 -21
  29. odxtools/diaglayercontainer.py +40 -40
  30. odxtools/diaglayerraw.py +92 -63
  31. odxtools/diagnostictroublecode.py +2 -2
  32. odxtools/diagservice.py +53 -35
  33. odxtools/docrevision.py +1 -1
  34. odxtools/dopbase.py +14 -3
  35. odxtools/dtcdop.py +15 -9
  36. odxtools/dynamiclengthfield.py +6 -4
  37. odxtools/endofpdufield.py +22 -23
  38. odxtools/environmentdata.py +2 -5
  39. odxtools/environmentdatadescription.py +6 -4
  40. odxtools/field.py +3 -8
  41. odxtools/isotp_state_machine.py +52 -38
  42. odxtools/leadinglengthinfotype.py +9 -7
  43. odxtools/load_file.py +2 -1
  44. odxtools/load_odx_d_file.py +2 -5
  45. odxtools/load_pdx_file.py +2 -6
  46. odxtools/message.py +11 -3
  47. odxtools/minmaxlengthtype.py +107 -78
  48. odxtools/modification.py +2 -2
  49. odxtools/multiplexer.py +23 -21
  50. odxtools/multiplexerswitchkey.py +37 -8
  51. odxtools/nameditemlist.py +59 -58
  52. odxtools/odxlink.py +4 -2
  53. odxtools/odxtypes.py +4 -3
  54. odxtools/parameterinfo.py +6 -6
  55. odxtools/parameters/codedconstparameter.py +15 -25
  56. odxtools/parameters/createanyparameter.py +1 -1
  57. odxtools/parameters/dynamicparameter.py +6 -5
  58. odxtools/parameters/lengthkeyparameter.py +2 -1
  59. odxtools/parameters/matchingrequestparameter.py +8 -11
  60. odxtools/parameters/nrcconstparameter.py +11 -21
  61. odxtools/parameters/parameter.py +4 -18
  62. odxtools/parameters/parameterwithdop.py +14 -29
  63. odxtools/parameters/physicalconstantparameter.py +7 -9
  64. odxtools/parameters/reservedparameter.py +17 -38
  65. odxtools/parameters/systemparameter.py +6 -5
  66. odxtools/parameters/tableentryparameter.py +6 -5
  67. odxtools/parameters/tablekeyparameter.py +8 -15
  68. odxtools/parameters/tablestructparameter.py +11 -12
  69. odxtools/parameters/valueparameter.py +9 -24
  70. odxtools/paramlengthinfotype.py +11 -9
  71. odxtools/physicaldimension.py +1 -1
  72. odxtools/physicaltype.py +2 -2
  73. odxtools/response.py +7 -3
  74. odxtools/singleecujob.py +48 -22
  75. odxtools/standardlengthtype.py +11 -6
  76. odxtools/uds.py +1 -1
  77. odxtools/unit.py +5 -5
  78. odxtools/unitgroup.py +1 -1
  79. odxtools/unitspec.py +2 -2
  80. odxtools/version.py +13 -3
  81. odxtools/write_pdx_file.py +7 -4
  82. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/METADATA +7 -5
  83. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/RECORD +87 -88
  84. odxtools/positioneddataobjectproperty.py +0 -74
  85. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/LICENSE +0 -0
  86. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/WHEEL +0 -0
  87. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/entry_points.txt +0 -0
  88. {odxtools-5.3.1.dist-info → odxtools-6.0.1.dist-info}/top_level.txt +0 -0
@@ -111,6 +111,7 @@ class CommunicationParameterRef:
111
111
  f"'{self.short_name}' are not specified "
112
112
  f"correctly.",
113
113
  OdxWarning,
114
+ stacklevel=1,
114
115
  )
115
116
  return None
116
117
 
@@ -122,6 +123,7 @@ class CommunicationParameterRef:
122
123
  f"Communication parameter '{self.short_name}' "
123
124
  f"does not specify a '{subparam_name}' sub-parameter.",
124
125
  OdxWarning,
126
+ stacklevel=1,
125
127
  )
126
128
  return None
127
129
 
@@ -134,9 +136,10 @@ class CommunicationParameterRef:
134
136
  return result
135
137
 
136
138
  @property
137
- def short_name(self):
139
+ def short_name(self) -> str:
138
140
  if self.comparam:
139
141
  return self.comparam.short_name
140
- # ODXLINK IDs allow dots and hyphens, but python identifiers do not.
141
- # This should not happen anyway in a correct PDX
142
+
143
+ # ODXLINK IDs allow dots and hyphens, but short names do not.
144
+ # (This should not happen anyway in a correct PDX...)
142
145
  return self.id_ref.ref_id.replace(".", "__").replace("-", "_")
@@ -54,7 +54,7 @@ class CompanyDocInfo:
54
54
 
55
55
  return result
56
56
 
57
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase):
57
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
58
58
  self._company_data = odxlinks.resolve(self.company_data_ref, CompanyData)
59
59
 
60
60
  self._team_member: Optional[TeamMember] = None
@@ -64,6 +64,6 @@ class CompanyDocInfo:
64
64
  for sdg in self.sdgs:
65
65
  sdg._resolve_odxlinks(odxlinks)
66
66
 
67
- def _resolve_snrefs(self, diag_layer: "DiagLayer"):
67
+ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
68
68
  for sdg in self.sdgs:
69
69
  sdg._resolve_snrefs(diag_layer)
@@ -36,7 +36,7 @@ class CompanyRevisionInfo:
36
36
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
37
37
  return {}
38
38
 
39
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase):
39
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
40
40
  self._company_data = odxlinks.resolve(self.company_data_ref, CompanyData)
41
41
 
42
42
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
@@ -87,8 +87,8 @@ class ComparamSubset(IdentifiableElement):
87
87
  for comparam in self.comparams:
88
88
  odxlinks.update(comparam._build_odxlinks())
89
89
 
90
- for comparam in self.complex_comparams:
91
- odxlinks.update(comparam._build_odxlinks())
90
+ for ccomparam in self.complex_comparams:
91
+ odxlinks.update(ccomparam._build_odxlinks())
92
92
 
93
93
  if self.unit_spec:
94
94
  odxlinks.update(self.unit_spec._build_odxlinks())
@@ -112,8 +112,8 @@ class ComparamSubset(IdentifiableElement):
112
112
  for comparam in self.comparams:
113
113
  comparam._resolve_odxlinks(odxlinks)
114
114
 
115
- for comparam in self.complex_comparams:
116
- comparam._resolve_odxlinks(odxlinks)
115
+ for ccomparam in self.complex_comparams:
116
+ ccomparam._resolve_odxlinks(odxlinks)
117
117
 
118
118
  if self.unit_spec:
119
119
  self.unit_spec._resolve_odxlinks(odxlinks)
@@ -135,8 +135,8 @@ class ComparamSubset(IdentifiableElement):
135
135
  for comparam in self.comparams:
136
136
  comparam._resolve_snrefs(diag_layer)
137
137
 
138
- for comparam in self.complex_comparams:
139
- comparam._resolve_snrefs(diag_layer)
138
+ for ccomparam in self.complex_comparams:
139
+ ccomparam._resolve_snrefs(diag_layer)
140
140
 
141
141
  if self.unit_spec:
142
142
  self.unit_spec._resolve_snrefs(diag_layer)
@@ -32,7 +32,7 @@ class ComplexComparam(BaseComparam):
32
32
 
33
33
  @property
34
34
  def allow_multiple_values(self) -> bool:
35
- return self.allow_multiple_values_raw == True
35
+ return self.allow_multiple_values_raw is True
36
36
 
37
37
  @staticmethod
38
38
  def from_et(et_element: ElementTree.Element,
@@ -1,9 +1,9 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import abc
3
3
  from dataclasses import dataclass
4
- from typing import Literal, Union
4
+ from typing import Literal
5
5
 
6
- from ..odxtypes import DataType
6
+ from ..odxtypes import AtomicOdxType, DataType
7
7
 
8
8
  CompuMethodCategory = Literal[
9
9
  "IDENTICAL",
@@ -24,17 +24,14 @@ class CompuMethod(abc.ABC):
24
24
  def category(self) -> CompuMethodCategory:
25
25
  pass
26
26
 
27
- def convert_physical_to_internal(self, physical_value):
27
+ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
28
28
  raise NotImplementedError()
29
29
 
30
- def convert_internal_to_physical(self, internal_value) -> Union[int, float, str]:
30
+ def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
31
31
  raise NotImplementedError()
32
32
 
33
- def is_valid_physical_value(self, physical_value):
33
+ def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
34
34
  raise NotImplementedError()
35
35
 
36
- def is_valid_internal_value(self, internal_value):
36
+ def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
37
37
  raise NotImplementedError()
38
-
39
- def get_valid_physical_values(self):
40
- return None
@@ -19,12 +19,12 @@ from .texttablecompumethod import TexttableCompuMethod
19
19
 
20
20
  def _parse_compu_scale_to_linear_compu_method(
21
21
  *,
22
- scale_element,
22
+ scale_element: ElementTree.Element,
23
23
  internal_type: DataType,
24
24
  physical_type: DataType,
25
- is_scale_linear=False,
26
- **kwargs,
27
- ):
25
+ is_scale_linear: bool = False,
26
+ **kwargs: Any,
27
+ ) -> LinearCompuMethod:
28
28
  odxassert(physical_type in [
29
29
  DataType.A_FLOAT32,
30
30
  DataType.A_FLOAT64,
@@ -47,18 +47,20 @@ def _parse_compu_scale_to_linear_compu_method(
47
47
  kwargs["internal_type"] = internal_type
48
48
  kwargs["physical_type"] = physical_type
49
49
 
50
- coeffs = scale_element.find("COMPU-RATIONAL-COEFFS")
50
+ coeffs = odxrequire(scale_element.find("COMPU-RATIONAL-COEFFS"))
51
51
  nums = coeffs.iterfind("COMPU-NUMERATOR/V")
52
52
 
53
- offset = computation_python_type(next(nums).text)
53
+ offset = computation_python_type(odxrequire(next(nums).text))
54
54
  factor_el = next(nums, None)
55
- factor = computation_python_type(factor_el.text if factor_el is not None else "0")
55
+ factor = computation_python_type(odxrequire(factor_el.text) if factor_el is not None else "0")
56
56
  denominator = 1.0
57
57
  if (string := coeffs.findtext("COMPU-DENOMINATOR/V")) is not None:
58
58
  denominator = float(string)
59
59
  if denominator == 0:
60
- warnings.warn("CompuMethod: A denominator of zero will lead to divisions by zero.",
61
- OdxWarning)
60
+ warnings.warn(
61
+ "CompuMethod: A denominator of zero will lead to divisions by zero.",
62
+ OdxWarning,
63
+ stacklevel=1)
62
64
  # Read lower limit
63
65
  internal_lower_limit = Limit.from_et(
64
66
  scale_element.find("LOWER-LIMIT"),
@@ -1,6 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
 
4
+ from ..odxtypes import AtomicOdxType
4
5
  from .compumethod import CompuMethod, CompuMethodCategory
5
6
 
6
7
 
@@ -11,14 +12,14 @@ class IdenticalCompuMethod(CompuMethod):
11
12
  def category(self) -> CompuMethodCategory:
12
13
  return "IDENTICAL"
13
14
 
14
- def convert_physical_to_internal(self, physical_value):
15
+ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
15
16
  return physical_value
16
17
 
17
- def convert_internal_to_physical(self, internal_value):
18
+ def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
18
19
  return internal_value
19
20
 
20
- def is_valid_physical_value(self, physical_value):
21
+ def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
21
22
  return self.physical_type.isinstance(physical_value)
22
23
 
23
- def is_valid_internal_value(self, internal_value):
24
+ def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
24
25
  return self.internal_type.isinstance(internal_value)
@@ -1,11 +1,11 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import Optional, Union
4
+ from typing import Optional
5
5
  from xml.etree import ElementTree
6
6
 
7
7
  from ..exceptions import odxassert, odxraise, odxrequire
8
- from ..odxtypes import DataType
8
+ from ..odxtypes import AtomicOdxType, DataType
9
9
 
10
10
 
11
11
  class IntervalType(Enum):
@@ -16,7 +16,7 @@ class IntervalType(Enum):
16
16
 
17
17
  @dataclass
18
18
  class Limit:
19
- value: Union[str, int, float, bytes]
19
+ value: AtomicOdxType
20
20
  interval_type: IntervalType = IntervalType.CLOSED
21
21
 
22
22
  def __post_init__(self) -> None:
@@ -53,7 +53,7 @@ class Limit:
53
53
  else:
54
54
  return Limit(internal_type.from_string(odxrequire(et_element.text)), interval_type)
55
55
 
56
- def complies_to_upper(self, value):
56
+ def complies_to_upper(self, value: AtomicOdxType) -> bool:
57
57
  """Checks if the value is in the range w.r.t. the upper limit.
58
58
 
59
59
  * If the interval type is closed, return `value <= limit.value`.
@@ -61,13 +61,13 @@ class Limit:
61
61
  * If the interval type is infinite, return `True`.
62
62
  """
63
63
  if self.interval_type == IntervalType.CLOSED:
64
- return value <= self.value
64
+ return value <= self.value # type: ignore[operator]
65
65
  elif self.interval_type == IntervalType.OPEN:
66
- return value < self.value
66
+ return value < self.value # type: ignore[operator]
67
67
  elif self.interval_type == IntervalType.INFINITE:
68
68
  return True
69
69
 
70
- def complies_to_lower(self, value):
70
+ def complies_to_lower(self, value: AtomicOdxType) -> bool:
71
71
  """Checks if the value is in the range w.r.t. the lower limit.
72
72
 
73
73
  * If the interval type is closed, return `limit.value <= value`.
@@ -75,8 +75,8 @@ class Limit:
75
75
  * If the interval type is infinite, return `True`.
76
76
  """
77
77
  if self.interval_type == IntervalType.CLOSED:
78
- return self.value <= value
78
+ return self.value <= value # type: ignore[operator]
79
79
  elif self.interval_type == IntervalType.OPEN:
80
- return self.value < value
80
+ return self.value < value # type: ignore[operator]
81
81
  elif self.interval_type == IntervalType.INFINITE:
82
82
  return True
@@ -1,9 +1,9 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Union
3
+ from typing import cast
4
4
 
5
- from ..exceptions import odxassert
6
- from ..odxtypes import DataType
5
+ from ..exceptions import DecodeError, EncodeError, odxassert
6
+ from ..odxtypes import AtomicOdxType, DataType
7
7
  from .compumethod import CompuMethod, CompuMethodCategory
8
8
  from .limit import IntervalType, Limit
9
9
 
@@ -73,20 +73,20 @@ class LinearCompuMethod(CompuMethod):
73
73
  return "LINEAR"
74
74
 
75
75
  @property
76
- def physical_lower_limit(self):
76
+ def physical_lower_limit(self) -> Limit:
77
77
  return self._physical_lower_limit
78
78
 
79
79
  @property
80
- def physical_upper_limit(self):
80
+ def physical_upper_limit(self) -> Limit:
81
81
  return self._physical_upper_limit
82
82
 
83
- def __compute_physical_limits(self):
83
+ def __compute_physical_limits(self) -> None:
84
84
  """Computes the physical limits and stores them in the properties
85
85
  self._physical_lower_limit and self._physical_upper_limit.
86
86
  This method is only called during the initialization of a LinearCompuMethod.
87
87
  """
88
88
 
89
- def convert_to_limit_to_physical(limit: Limit, is_upper_limit: bool):
89
+ def convert_to_limit_to_physical(limit: Limit, is_upper_limit: bool) -> Limit:
90
90
  """Helper method
91
91
 
92
92
  Parameters:
@@ -127,10 +127,14 @@ class LinearCompuMethod(CompuMethod):
127
127
  if self.physical_type == DataType.A_UINT32:
128
128
  # If the data type is unsigned, the physical lower limit should be at least 0.
129
129
  if (self._physical_lower_limit.interval_type == IntervalType.INFINITE or
130
- self._physical_lower_limit.value < 0):
130
+ cast(float, self._physical_lower_limit.value) < 0):
131
131
  self._physical_lower_limit = Limit(value=0, interval_type=IntervalType.CLOSED)
132
132
 
133
- def _convert_internal_to_physical(self, internal_value):
133
+ def _convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
134
+ if not isinstance(internal_value, (int, float)):
135
+ raise DecodeError("The type of internal values of linear compumethods must "
136
+ "either int or float")
137
+
134
138
  if self.denominator is None:
135
139
  result = self.offset + self.factor * internal_value
136
140
  else:
@@ -143,11 +147,15 @@ class LinearCompuMethod(CompuMethod):
143
147
  result = round(result)
144
148
  return self.physical_type.make_from(result)
145
149
 
146
- def convert_internal_to_physical(self, internal_value) -> Union[int, float]:
150
+ def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
147
151
  odxassert(self.is_valid_internal_value(internal_value))
148
152
  return self._convert_internal_to_physical(internal_value)
149
153
 
150
- def convert_physical_to_internal(self, physical_value):
154
+ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
155
+ if not isinstance(physical_value, (int, float)):
156
+ raise EncodeError("The type of physical values of linear compumethods must "
157
+ "either int or float")
158
+
151
159
  odxassert(
152
160
  self.is_valid_physical_value(physical_value),
153
161
  f"physical value {physical_value} of type {type(physical_value)} "
@@ -165,12 +173,12 @@ class LinearCompuMethod(CompuMethod):
165
173
  result = round(result)
166
174
  return self.internal_type.make_from(result)
167
175
 
168
- def is_valid_physical_value(self, physical_value):
176
+ def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
169
177
  # Do type checks
170
178
  expected_type = self.physical_type.as_python_type()
171
- if expected_type == float and type(physical_value) not in [int, float]:
179
+ if expected_type == float and not isinstance(physical_value, (int, float)):
172
180
  return False
173
- elif expected_type != float and type(physical_value) != expected_type:
181
+ elif expected_type != float and not isinstance(physical_value, expected_type):
174
182
  return False
175
183
 
176
184
  # Compare to the limits
@@ -180,11 +188,11 @@ class LinearCompuMethod(CompuMethod):
180
188
  return False
181
189
  return True
182
190
 
183
- def is_valid_internal_value(self, internal_value):
191
+ def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
184
192
  expected_type = self.internal_type.as_python_type()
185
- if expected_type == float and type(internal_value) not in [int, float]:
193
+ if expected_type == float and not isinstance(internal_value, (int, float)):
186
194
  return False
187
- elif expected_type != float and type(internal_value) != expected_type:
195
+ elif expected_type != float and not isinstance(internal_value, expected_type):
188
196
  return False
189
197
 
190
198
  if not self.internal_lower_limit.complies_to_lower(internal_value):
@@ -3,6 +3,7 @@ from dataclasses import dataclass
3
3
  from typing import List
4
4
 
5
5
  from ..exceptions import odxassert
6
+ from ..odxtypes import AtomicOdxType
6
7
  from .compumethod import CompuMethod, CompuMethodCategory
7
8
  from .linearcompumethod import LinearCompuMethod
8
9
 
@@ -15,24 +16,24 @@ class ScaleLinearCompuMethod(CompuMethod):
15
16
  def category(self) -> CompuMethodCategory:
16
17
  return "SCALE-LINEAR"
17
18
 
18
- def convert_physical_to_internal(self, physical_value):
19
+ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
19
20
  odxassert(
20
21
  self.is_valid_physical_value(physical_value),
21
- f"cannot convert the invalid physical value {physical_value} "
22
+ f"cannot convert the invalid physical value {physical_value!r} "
22
23
  f"of type {type(physical_value)}")
23
24
  lin_method = next(
24
25
  scale for scale in self.linear_methods if scale.is_valid_physical_value(physical_value))
25
26
  return lin_method.convert_physical_to_internal(physical_value)
26
27
 
27
- def convert_internal_to_physical(self, internal_value):
28
+ def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
28
29
  lin_method = next(
29
30
  scale for scale in self.linear_methods if scale.is_valid_internal_value(internal_value))
30
31
  return lin_method.convert_internal_to_physical(internal_value)
31
32
 
32
- def is_valid_physical_value(self, physical_value):
33
+ def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
33
34
  return any(
34
35
  True for scale in self.linear_methods if scale.is_valid_physical_value(physical_value))
35
36
 
36
- def is_valid_internal_value(self, internal_value):
37
+ def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
37
38
  return any(
38
39
  True for scale in self.linear_methods if scale.is_valid_internal_value(internal_value))
@@ -3,7 +3,7 @@ from dataclasses import dataclass
3
3
  from typing import List, Tuple, Union
4
4
 
5
5
  from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
6
- from ..odxtypes import DataType
6
+ from ..odxtypes import AtomicOdxType, DataType
7
7
  from .compumethod import CompuMethod, CompuMethodCategory
8
8
  from .limit import IntervalType, Limit
9
9
 
@@ -115,34 +115,55 @@ class TabIntpCompuMethod(CompuMethod):
115
115
 
116
116
  return None
117
117
 
118
- def convert_physical_to_internal(self, physical_value: Union[int, float]) -> Union[int, float]:
118
+ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
119
+ if not isinstance(physical_value, (int, float)):
120
+ raise EncodeError("The type of values of tab-intp compumethods must "
121
+ "either int or float")
122
+
119
123
  reference_points = list(zip(self.physical_points, self.internal_points))
120
- result = self._piecewise_linear_interpolate(physical_value, reference_points)
124
+ odxassert(
125
+ isinstance(physical_value, (int, float)),
126
+ "Only integers and floats can be piecewise linearly interpolated")
127
+ result = self._piecewise_linear_interpolate(
128
+ physical_value, # type: ignore[arg-type]
129
+ reference_points)
121
130
 
122
131
  if result is None:
123
- raise EncodeError(f"Internal value {physical_value} must be inside the range"
132
+ raise EncodeError(f"Internal value {physical_value!r} must be inside the range"
124
133
  f" [{min(self.physical_points)}, {max(self.physical_points)}]")
125
134
  res = self.internal_type.make_from(result)
126
135
  if not isinstance(res, (int, float)):
127
136
  odxraise()
128
137
  return res
129
138
 
130
- def convert_internal_to_physical(self, internal_value: Union[int, float]) -> Union[int, float]:
139
+ def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
140
+ if not isinstance(internal_value, (int, float)):
141
+ raise EncodeError("The internal type of values of tab-intp compumethods must "
142
+ "either int or float")
143
+
131
144
  reference_points = list(zip(self.internal_points, self.physical_points))
132
- result = self._piecewise_linear_interpolate(internal_value, reference_points)
145
+ result = self._piecewise_linear_interpolate(
146
+ internal_value, # type: ignore[arg-type]
147
+ reference_points)
133
148
 
134
149
  if result is None:
135
- raise DecodeError(f"Internal value {internal_value} must be inside the range"
150
+ raise DecodeError(f"Internal value {internal_value!r} must be inside the range"
136
151
  f" [{min(self.internal_points)}, {max(self.internal_points)}]")
137
152
  res = self.physical_type.make_from(result)
138
153
  if not isinstance(res, (int, float)):
139
154
  odxraise()
140
155
  return res
141
156
 
142
- def is_valid_physical_value(self, physical_value: Union[int, float]) -> bool:
157
+ def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
158
+ if not isinstance(physical_value, (int, float)):
159
+ return False
160
+
143
161
  return min(self.physical_points) <= physical_value and physical_value <= max(
144
162
  self.physical_points)
145
163
 
146
- def is_valid_internal_value(self, internal_value: Union[int, float]) -> bool:
164
+ def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
165
+ if not isinstance(internal_value, (int, float)):
166
+ return False
167
+
147
168
  return min(self.internal_points) <= internal_value and internal_value <= max(
148
169
  self.internal_points)
@@ -3,7 +3,7 @@ from dataclasses import dataclass
3
3
  from typing import List, Optional
4
4
 
5
5
  from ..exceptions import DecodeError, EncodeError, odxassert
6
- from ..odxtypes import DataType
6
+ from ..odxtypes import AtomicOdxType, DataType
7
7
  from .compumethod import CompuMethod, CompuMethodCategory
8
8
  from .compuscale import CompuScale
9
9
 
@@ -28,25 +28,26 @@ class TexttableCompuMethod(CompuMethod):
28
28
  def category(self) -> CompuMethodCategory:
29
29
  return "TEXTTABLE"
30
30
 
31
- def _get_scales(self):
31
+ def get_scales(self) -> List[CompuScale]:
32
32
  scales = list(self.internal_to_phys)
33
33
  if self.compu_default_value:
34
34
  # Default is last, since it's a fallback
35
35
  scales.append(self.compu_default_value)
36
36
  return scales
37
37
 
38
- def convert_physical_to_internal(self, physical_value):
39
- scale: CompuScale = next(
40
- filter(lambda scale: scale.compu_const == physical_value, self._get_scales()), None)
41
- if scale is not None:
42
- res = (
43
- scale.compu_inverse_value
44
- if scale.compu_inverse_value is not None else scale.lower_limit.value)
45
- odxassert(self.internal_type.isinstance(res))
46
- return res
47
- raise EncodeError(f"Texttable compu method could not encode '{physical_value}'.")
38
+ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
39
+ matching_scales = [x for x in self.get_scales() if x.compu_const == physical_value]
40
+ for scale in matching_scales:
41
+ if scale.compu_inverse_value is not None:
42
+ return scale.compu_inverse_value
43
+ elif scale.lower_limit is not None:
44
+ return scale.lower_limit.value
45
+ elif scale.upper_limit is not None:
46
+ return scale.upper_limit.value
48
47
 
49
- def __is_internal_in_scale(self, internal_value, scale: CompuScale):
48
+ raise EncodeError(f"Texttable compu method could not encode '{physical_value!r}'.")
49
+
50
+ def __is_internal_in_scale(self, internal_value: AtomicOdxType, scale: CompuScale) -> bool:
50
51
  if scale == self.compu_default_value:
51
52
  return True
52
53
  if scale.lower_limit is not None and not scale.lower_limit.complies_to_lower(
@@ -60,23 +61,20 @@ class TexttableCompuMethod(CompuMethod):
60
61
  # value complies to the defined limits
61
62
  return True
62
63
 
63
- def convert_internal_to_physical(self, internal_value):
64
+ def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
64
65
  scale = next(
65
66
  filter(
66
67
  lambda scale: self.__is_internal_in_scale(internal_value, scale),
67
- self._get_scales(),
68
+ self.get_scales(),
68
69
  ), None)
69
- if scale is None:
70
+ if scale is None or scale.compu_const is None:
70
71
  raise DecodeError(
71
- f"Texttable compu method could not decode {internal_value} to string.")
72
+ f"Texttable compu method could not decode {internal_value!r} to string.")
72
73
  return scale.compu_const
73
74
 
74
- def is_valid_physical_value(self, physical_value):
75
- return physical_value in self.get_valid_physical_values()
75
+ def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
76
+ return any(x.compu_const == physical_value for x in self.get_scales())
76
77
 
77
- def is_valid_internal_value(self, internal_value):
78
+ def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
78
79
  return any(
79
- self.__is_internal_in_scale(internal_value, scale) for scale in self._get_scales())
80
-
81
- def get_valid_physical_values(self):
82
- return [x.compu_const for x in self._get_scales()]
80
+ self.__is_internal_in_scale(internal_value, scale) for scale in self.get_scales())
odxtools/database.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from itertools import chain
3
3
  from pathlib import Path
4
- from typing import List, Optional
4
+ from typing import List, Optional, Tuple
5
5
  from xml.etree import ElementTree
6
6
  from zipfile import ZipFile
7
7
 
@@ -13,7 +13,7 @@ from .nameditemlist import NamedItemList, short_name_as_key
13
13
  from .odxlink import OdxLinkDatabase
14
14
 
15
15
 
16
- def version(v: str):
16
+ def version(v: str) -> Tuple[int, ...]:
17
17
  return tuple(map(int, (v.split("."))))
18
18
 
19
19
 
@@ -135,13 +135,13 @@ class Database:
135
135
  return self._diag_layers
136
136
 
137
137
  @property
138
- def diag_layer_containers(self):
138
+ def diag_layer_containers(self) -> NamedItemList[DiagLayerContainer]:
139
139
  return self._diag_layer_containers
140
140
 
141
141
  @diag_layer_containers.setter
142
- def diag_layer_containers(self, value):
142
+ def diag_layer_containers(self, value: NamedItemList[DiagLayerContainer]) -> None:
143
143
  self._diag_layer_containers = value
144
144
 
145
145
  @property
146
- def comparam_subsets(self):
146
+ def comparam_subsets(self) -> NamedItemList[ComparamSubset]:
147
147
  return self._comparam_subsets