Skip to content

Commit 63aee5f

Browse files
committed
Reference class refactoring
- Enable implicit casting between reference types - Extract reference classes to new submodule
1 parent c37a7eb commit 63aee5f

File tree

3 files changed

+811
-664
lines changed

3 files changed

+811
-664
lines changed

src/autosar/xml/base.py

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
"""
2+
AUTOSAR XML base classes
3+
"""
4+
5+
import abc
6+
import re
7+
from typing import Any, Type
8+
from enum import Enum
9+
import autosar.xml.enumeration as ar_enum
10+
11+
12+
class ARObject:
13+
"""
14+
Base class for all AUTOSAR objects
15+
"""
16+
17+
@property
18+
def is_empty(self) -> bool:
19+
"""
20+
True if no value has been set (everything is None)
21+
"""
22+
for value in vars(self).values():
23+
if isinstance(value, list):
24+
if len(value) > 0:
25+
return False
26+
else:
27+
if value is not None:
28+
return False
29+
return True
30+
31+
def is_empty_with_ignore(self, ignore_set: set) -> bool:
32+
"""
33+
Same as is_empty but the caller can give
34+
a list of property names to ignore during
35+
check
36+
"""
37+
for key in vars(self).keys():
38+
if key not in ignore_set:
39+
value = getattr(self, key)
40+
if isinstance(value, list):
41+
if len(value) > 0:
42+
return False
43+
else:
44+
if value is not None:
45+
return False
46+
return True
47+
48+
def _assign_optional(self, attr_name: str, value: Any, type_name: type) -> None:
49+
"""
50+
Same as _assign but with a None-check
51+
"""
52+
if value is not None:
53+
self._assign(attr_name, value, type_name)
54+
55+
def _assign(self, attr_name: str, value: Any, type_name: type) -> None:
56+
"""
57+
Assign single value to attribute with type check.
58+
"""
59+
if issubclass(type_name, Enum):
60+
self._set_attr_with_strict_type(attr_name, value, type_name)
61+
elif issubclass(type_name, BaseRef):
62+
self._check_and_set_reference(attr_name, value, type_name)
63+
else:
64+
self._set_attr_with_type_cast(attr_name, value, type_name)
65+
66+
def _assign_int_or_str_pattern_optional(self, attr_name: str, value: int | str | None, pattern: re.Pattern) -> None:
67+
"""
68+
Same as _assign_int_or_str_pattern but with a None-check
69+
"""
70+
if value is not None:
71+
self._assign_int_or_str_pattern(attr_name, value, pattern)
72+
73+
def _assign_int_or_str_pattern(self, attr_name: str, value: int | str, pattern: re.Pattern) -> None:
74+
"""
75+
Special assignment-function for values that can be either int or conforms
76+
to a specific regular expression
77+
"""
78+
if isinstance(value, int):
79+
pass
80+
elif isinstance(value, str):
81+
match = pattern.match(value)
82+
if match is None:
83+
raise ValueError(f"Invalid parameter '{value}' for '{attr_name}'")
84+
else:
85+
raise TypeError(f"{attr_name}: Invalid type. Expected (int, str), got '{str(type(value))}'")
86+
setattr(self, attr_name, value)
87+
88+
def _assign_optional_strict(self, attr_name: str, value: Any, type_name: type) -> None:
89+
"""
90+
Sets object attribute with strict type check
91+
"""
92+
if value is not None:
93+
self._set_attr_with_strict_type(attr_name, value, type_name)
94+
95+
def _assign_optional_positive_int(self, attr_name, value: int) -> None:
96+
"""
97+
Checks that the optional value is a positive integer before assignment
98+
"""
99+
if value is not None:
100+
self._set_attr_positive_int(attr_name, value)
101+
102+
def _set_attr_with_strict_type(self, attr_name: str, value: Any, type_class: type) -> None:
103+
"""
104+
Sets object attribute only if the value is matches given type-class
105+
"""
106+
if isinstance(value, type_class):
107+
setattr(self, attr_name, value)
108+
else:
109+
raise TypeError(
110+
f"Invalid type for parameter '{attr_name}'. Expected type {str(type_class)}, got {str(type(value))}")
111+
112+
def _check_and_set_reference(self, attr_name: str, value: Any, ref_type: Type["BaseRef"]):
113+
"""
114+
Checks reference type compatibility and on success updates the value
115+
"""
116+
accepted_sub_types: set = ref_type.accepted_sub_types()
117+
if (len(accepted_sub_types) == 0) or not isinstance(accepted_sub_types, set):
118+
msg = f"Error in reference type '{str(type(ref_type))}', it doesn't seem to have a valid set of sub-types."
119+
raise RuntimeError(msg)
120+
if isinstance(value, str):
121+
if len(accepted_sub_types) == 1:
122+
new_value = ref_type(value, list(accepted_sub_types)[0])
123+
else:
124+
raise TypeError("Ambigious value for DEST. Unable to create reference directly from string")
125+
elif isinstance(value, BaseRef):
126+
if type(value) is ref_type or value.dest in accepted_sub_types: # pylint: disable=C0123
127+
new_value = ref_type(value.value, value.dest)
128+
else:
129+
raise TypeError(f"'{attr_name}': Reference type {str(type(value))}"
130+
f"isn't combatible with {str(ref_type)}")
131+
else:
132+
raise TypeError(f"'{attr_name}': Invalid type. "
133+
f"Expected one of (str, {str(ref_type)}), got '{str(type(value))}'")
134+
setattr(self, attr_name, new_value)
135+
136+
def _set_attr_with_type_cast(self, attr_name: str, value: Any, type_class: type) -> None:
137+
"""
138+
Sets object attribute only if it can be converted to given type.
139+
"""
140+
if type_class in {bool, int, str, float}:
141+
new_value = type_class(value)
142+
else:
143+
raise NotImplementedError(type_class)
144+
setattr(self, attr_name, new_value)
145+
146+
def _set_attr_positive_int(self, attr_name: str, value: int) -> None:
147+
"""
148+
Checks that value is non-negative before updating attribute
149+
"""
150+
if not isinstance(value, int):
151+
raise TypeError(f"Invalid type for '{attr_name}'. Expected int, got '{str(type(value))}'")
152+
if value < 0:
153+
raise ValueError(f"Positive integer expected: {value}")
154+
setattr(self, attr_name, value)
155+
156+
def _find_by_name(self, elements: list, name: str):
157+
"""
158+
Iterates through list of elements and return the first whose
159+
name matches the name argument
160+
"""
161+
for elem in elements:
162+
if elem.name == name:
163+
return elem
164+
return None
165+
166+
167+
class BaseRef(ARObject, abc.ABC):
168+
"""
169+
Base type for all reference classes
170+
"""
171+
172+
def __init__(self,
173+
value: str,
174+
dest: ar_enum.IdentifiableSubTypes = None) -> None:
175+
self.value = value
176+
self.dest: ar_enum.IdentifiableSubTypes = None
177+
if dest is None:
178+
if len(self.accepted_sub_types()) == 1:
179+
dest = list(self.accepted_sub_types())[0]
180+
else:
181+
msg_part1 = "Value of dest cannot be None. Accepted values are: "
182+
msg_part2 = ",".join([str(x) for x in sorted(list(self.accepted_sub_types()))])
183+
raise ValueError(msg_part1 + msg_part2)
184+
if dest in self.accepted_sub_types():
185+
self.dest = dest
186+
else:
187+
raise ValueError(f"{str(dest)} is not a valid sub-type for {str(type(self))}")
188+
189+
@classmethod
190+
@abc.abstractmethod
191+
def accepted_sub_types(cls) -> list[ar_enum.IdentifiableSubTypes]:
192+
"""
193+
Subset of ar_enum.IdentifiableSubTypes defining
194+
which enum values are acceptable for dest
195+
"""
196+
197+
def __str__(self) -> str:
198+
"""Returns reference as string"""
199+
return self.value

0 commit comments

Comments
 (0)