Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/select.py: 26%

67 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-16 22:16 +0000

1import enum 

2import typing as t 

3from collections import OrderedDict 

4from numbers import Number 

5from viur.core.config import conf 

6from viur.core.bones.base import BaseBone, ReadFromClientError, ReadFromClientErrorSeverity 

7from viur.core.i18n import translate 

8 

9try: 

10 from typing import Self # only py>=3.11 

11except ImportError: 

12 Self = BaseBone # SelectBone is not defined here and Self is not available 

13 

14if t.TYPE_CHECKING: 14 ↛ 15line 14 didn't jump to line 15 because the condition on line 14 was never true

15 from viur.core.skeleton import SkeletonInstance 

16 

17SelectBoneValue = t.Union[str, Number, enum.Enum] 

18""" 

19Type alias of possible values in a SelectBone. SelectBoneValue can be either a string (str) or a number (Number) 

20""" 

21 

22SelectBoneMultiple = list[SelectBoneValue] 

23"""Type alias for values of a multiple SelectBone.""" 

24 

25 

26def translation_key_prefix_skeleton_bonename(bones_instance: BaseBone) -> str: 

27 """Generate a translation key prefix based on the skeleton name""" 

28 return f'skeleton.{bones_instance.skel_cls.__name__.lower().removesuffix("skel")}.{bones_instance.name}.' 

29 

30 

31def translation_key_prefix_bonename(bones_instance: BaseBone) -> str: 

32 """Generate a translation key prefix based on the skeleton and bone name""" 

33 return f'skeleton.{bones_instance.skel_cls.__name__.lower().removesuffix("skel")}.{bones_instance.name}.' 

34 

35 

36class SelectBone(BaseBone): 

37 """ 

38 A SelectBone is a bone which can take a value from a certain list of values. 

39 Inherits from the BaseBone class. The `type` attribute is set to "select". 

40 """ 

41 type = "select" 

42 

43 def __init__( 

44 self, 

45 *, 

46 defaultValue: t.Union[ 

47 SelectBoneValue, 

48 SelectBoneMultiple, 

49 t.Dict[str, t.Union[SelectBoneMultiple, SelectBoneValue]], 

50 t.Callable[["SkeletonInstance", Self], t.Any], 

51 ] = None, 

52 values: dict | list | tuple | t.Callable | enum.EnumMeta = (), 

53 translation_key_prefix: str | t.Callable[[Self], str] = "", 

54 **kwargs 

55 ): 

56 """ 

57 Initializes a new SelectBone. 

58 

59 :param defaultValue: key(s) of the values which will be checked by default. 

60 :param values: dict of key->value pairs from which the user can choose from 

61 -- or a callable that returns a dict. 

62 :param translation_key_prefix: A prefix for the key of the translation object. 

63 It is empty by default, so that only the label (dict value) from the values is used. 

64 A static string or dynamic method can be used (like `translation_key_prefix_bonename`). 

65 :param kwargs: Additional keyword arguments that will be passed to the superclass' __init__ method. 

66 """ 

67 super().__init__(defaultValue=defaultValue, **kwargs) 

68 self.translation_key_prefix = translation_key_prefix 

69 

70 # handle list/tuple as dicts 

71 if isinstance(values, (list, tuple)): 

72 values = {value: value for value in values} 

73 

74 assert isinstance(values, (dict, OrderedDict)) or callable(values) 

75 self._values = values 

76 

77 def __getattribute__(self, item): 

78 """ 

79 Overrides the default __getattribute__ method to handle the 'values' attribute dynamically. If the '_values' 

80 attribute is callable, it will be called and the result will be stored in the 'values' attribute. 

81 

82 :param str item: The attribute name. 

83 :return: The value of the specified attribute. 

84 

85 :raises AssertionError: If the resulting values are not of type dict or OrderedDict. 

86 """ 

87 if item == "values": 

88 values = self._values 

89 if isinstance(values, enum.EnumMeta): 

90 values = {value.value: value.name for value in values} 

91 elif callable(values): 

92 values = values() 

93 

94 # handle list/tuple as dicts 

95 if isinstance(values, (list, tuple)): 

96 values = {value: value for value in values} 

97 

98 assert isinstance(values, (dict, OrderedDict)) 

99 

100 prefix = self.translation_key_prefix 

101 if callable(prefix): 

102 prefix = prefix(self) 

103 

104 values = { 

105 key: label if isinstance(label, translate) else translate( 

106 f"{prefix}{label}", str(label), 

107 f"value {key} for {self.name}<{type(self).__name__}> in {self.skel_cls.__name__} in {self.skel_cls}" 

108 ) 

109 for key, label in values.items() 

110 } 

111 

112 return values 

113 

114 return super().__getattribute__(item) 

115 

116 def singleValueUnserialize(self, val): 

117 if isinstance(self._values, enum.EnumMeta): 

118 for value in self._values: 

119 if value.value == val: 

120 return value 

121 return val 

122 

123 def singleValueSerialize(self, val, skel: 'SkeletonInstance', name: str, parentIndexed: bool): 

124 if isinstance(self._values, enum.EnumMeta) and isinstance(val, self._values): 

125 return val.value 

126 return val 

127 

128 def singleValueFromClient(self, value, skel, bone_name, client_data): 

129 if not str(value): 

130 return self.getEmptyValue(), [ReadFromClientError(ReadFromClientErrorSeverity.Empty, "No value selected")] 

131 for key in self.values.keys(): 

132 if str(key) == str(value): 

133 if isinstance(self._values, enum.EnumMeta): 

134 return self._values(key), None 

135 return key, None 

136 return self.getEmptyValue(), [ 

137 ReadFromClientError(ReadFromClientErrorSeverity.Invalid, "Invalid value selected")] 

138 

139 def structure(self) -> dict: 

140 return super().structure() | { 

141 "values": 

142 {k: str(v) for k, v in self.values.items()} # new-style dict 

143 if "bone.select.structure.values.keytuple" not in conf.compatibility 

144 else [(k, str(v)) for k, v in self.values.items()] # old-style key-tuple 

145 }