Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/boolean.py: 20%

45 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-27 07:59 +0000

1import typing as t 

2 

3from viur.core import conf, db, utils 

4from viur.core.bones.base import BaseBone 

5 

6DEFAULT_VALUE_T: t.TypeAlias = bool | None | list[bool] | dict[str, list[bool] | bool] 

7 

8 

9class BooleanBone(BaseBone): 

10 """ 

11 Represents a boolean data type, which can have two possible values: `True` or `False`. 

12 It also allows for `None` to specify the "not yet set"-state. 

13 BooleanBones cannot be defined as `multiple=True`. 

14 

15 :param defaultValue: The default value of the `BooleanBone` instance. Defaults to `None` (unset). 

16 :raises ValueError: If the `defaultValue` is not either a boolean value (`True` or `False`) or `None`. 

17 """ 

18 type = "bool" 

19 

20 def __init__( 

21 self, 

22 *, 

23 defaultValue: DEFAULT_VALUE_T | t.Callable[[t.Self, "SkeletonInstance"], DEFAULT_VALUE_T] = None, 

24 **kwargs 

25 ): 

26 if defaultValue is not None: 

27 # We have given an explicit defaultValue and maybe a complex structure 

28 if not kwargs.get("languages") and not (isinstance(defaultValue, bool) or callable(defaultValue)): 

29 raise TypeError("Only True, False, None or callable can be provided as BooleanBone defaultValue") 

30 # TODO: missing validation for complex types, but in other bones too 

31 

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

33 

34 # Disallow creation of BooleanBone(multiple=True) 

35 if self.multiple: 

36 raise ValueError("BooleanBone cannot be multiple") 

37 

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

39 return utils.parse.bool(value, conf.bone_boolean_str2true), None 

40 

41 def getEmptyValue(self): 

42 """ 

43 Returns the empty value of the `BooleanBone` class, which is `False`. 

44 

45 :return: The empty value of the `BooleanBone` class (`False`). 

46 :rtype: bool 

47 """ 

48 return False 

49 

50 def isEmpty(self, value: t.Any): 

51 """ 

52 Checks if the given boolean value is empty. 

53 

54 :param value: The boolean value to be checked. 

55 :return: `True` if the boolean value is empty (i.e., equal to the empty value of the `BooleanBone` class), \ 

56 `False` otherwise. 

57 :rtype: bool 

58 """ 

59 if value is self.getEmptyValue(): 

60 return True 

61 return not bool(value) 

62 

63 def refresh(self, skel: 'viur.core.skeleton.SkeletonInstance', name: str) -> None: 

64 """ 

65 Inverse of serialize. Evaluates whats 

66 read from the datastore and populates 

67 this bone accordingly. 

68 

69 :param name: The property-name this bone has in its Skeleton (not the description!) 

70 """ 

71 if self.languages: 

72 for lang in self.languages: 

73 skel[name][lang] = utils.parse.bool(skel[name][lang], conf.bone_boolean_str2true) \ 

74 if lang in skel[name] else self.getDefaultValue(skel) 

75 elif skel[name] != self.getEmptyValue(): 

76 # Enforce a boolean if the bone is not empty (Maybe the empty value is explicit set to None). 

77 # So in this case we keep the empty value (e.g. the None) as is. 

78 skel[name] = utils.parse.bool(skel[name], conf.bone_boolean_str2true) 

79 

80 def setBoneValue( 

81 self, 

82 skel: 'SkeletonInstance', 

83 boneName: str, 

84 value: t.Any, 

85 append: bool, 

86 language: None | str = None 

87 ) -> bool: 

88 """ 

89 Sets the value of the bone to the provided 'value'. 

90 Sanity checks are performed; if the value is invalid, the bone value will revert to its original 

91 (default) value and the function will return False. 

92 

93 :param skel: Dictionary with the current values from the skeleton the bone belongs to 

94 :param boneName: The name of the bone that should be modified 

95 :param value: The value that should be assigned. Its type depends on the type of the bone 

96 :param append: If True, the given value will be appended to the existing bone values instead of 

97 replacing them. Only supported on bones with multiple=True 

98 :param language: Optional, the language of the value if the bone is language-aware 

99 :return: A boolean indicating whether the operation succeeded or not 

100 :rtype: bool 

101 """ 

102 if append: 

103 raise ValueError(f"append is not possible on {self.type} bones") 

104 

105 if language: 

106 if not self.languages or language not in self.languages: 

107 return False 

108 

109 skel[boneName][language] = utils.parse.bool(value) 

110 else: 

111 skel[boneName] = utils.parse.bool(value) 

112 

113 return True 

114 

115 def singleValueSerialize(self, value, skel: 'SkeletonInstance', name: str, parentIndexed: bool): 

116 """ 

117 Serializes a single value of the bone for storage in the database. 

118 

119 Derived bone classes should overwrite this method to implement their own logic for serializing single 

120 values. 

121 The serialized value should be suitable for storage in the database. 

122 """ 

123 if value == self.getEmptyValue(): 

124 # Keep the bones empty, maybe the empty value is explicit set to None 

125 return value 

126 return utils.parse.bool(value, conf.bone_boolean_str2true) 

127 

128 def buildDBFilter( 

129 self, 

130 name: str, 

131 skel: 'viur.core.skeleton.SkeletonInstance', 

132 dbFilter: db.Query, 

133 rawFilter: dict, 

134 prefix: t.Optional[str] = None 

135 ) -> db.Query: 

136 """ 

137 Builds a database filter based on the boolean value. 

138 

139 :param name: The name of the `BooleanBone` instance. 

140 :param skel: The `SkeletonInstance` object representing the data of the current entity. 

141 :param dbFilter: The `Query` object representing the current database filter. 

142 :param rawFilter: The dictionary representing the raw filter data received from the client. 

143 :param prefix: A prefix to be added to the property name in the database filter. 

144 :return: The updated `Query` object representing the updated database filter. 

145 :rtype: google.cloud.ndb.query.Query 

146 """ 

147 if name in rawFilter: 

148 val = utils.parse.bool(rawFilter[name], conf.bone_boolean_str2true) 

149 return super().buildDBFilter(name, skel, dbFilter, {name: val}, prefix=prefix) 

150 

151 return dbFilter