Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/boolean.py: 75%
51 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-25 15:02 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-25 15:02 +0000
1import typing as t
3from viur.core import conf, db, utils
4from viur.core.bones.base import BaseBone, ReadFromClientError, ReadFromClientErrorSeverity
6DEFAULT_VALUE_T: t.TypeAlias = bool | None | list[bool] | dict[str, list[bool] | bool]
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`.
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"
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
32 super().__init__(defaultValue=defaultValue, **kwargs)
34 # Disallow creation of BooleanBone(multiple=True)
35 if self.multiple:
36 raise ValueError("BooleanBone cannot be multiple")
38 def singleValueFromClient(self, value, skel, bone_name, client_data):
39 value = utils.parse.bool(value, conf.bone_boolean_str2true)
41 if err := self.isInvalid(value): 41 ↛ 42line 41 didn't jump to line 42 because the condition on line 41 was never true
42 return value, [ReadFromClientError(ReadFromClientErrorSeverity.Invalid, err)]
44 return value, None
46 def getEmptyValue(self):
47 """
48 Returns the empty value of the `BooleanBone` class, which is `False`.
50 :return: The empty value of the `BooleanBone` class (`False`).
51 :rtype: bool
52 """
53 return False
55 def isEmpty(self, value: t.Any):
56 """
57 Checks if the given boolean value is empty.
59 :param value: The boolean value to be checked.
60 :return: `True` if the boolean value is empty (i.e., equal to the empty value of the `BooleanBone` class), \
61 `False` otherwise.
62 :rtype: bool
63 """
64 if value is self.getEmptyValue():
65 return True
66 return not bool(value)
68 def refresh(self, skel: 'viur.core.skeleton.SkeletonInstance', name: str) -> None:
69 """
70 Inverse of serialize. Evaluates whats
71 read from the datastore and populates
72 this bone accordingly.
74 :param name: The property-name this bone has in its Skeleton (not the description!)
75 """
76 if self.languages: 76 ↛ 90line 76 didn't jump to line 90 because the condition on line 76 was always true
77 # The raw datastore value can be None (entity written before this bone existed) and
78 # getDefaultValue() answers with a dict for a language-aware bone, so neither may be
79 # indexed blindly.
80 values = skel[name] or {}
81 defaults = self.getDefaultValue(skel)
82 if not isinstance(defaults, dict): 82 ↛ 83line 82 didn't jump to line 83 because the condition on line 82 was never true
83 defaults = {lang: defaults for lang in self.languages}
85 skel[name] = {
86 lang: utils.parse.bool(values[lang], conf.bone_boolean_str2true)
87 if lang in values else defaults.get(lang)
88 for lang in self.languages
89 }
90 elif skel[name] != self.getEmptyValue():
91 # Enforce a boolean if the bone is not empty (Maybe the empty value is explicit set to None).
92 # So in this case we keep the empty value (e.g. the None) as is.
93 skel[name] = utils.parse.bool(skel[name], conf.bone_boolean_str2true)
95 def setBoneValue(
96 self,
97 skel: 'SkeletonInstance',
98 boneName: str,
99 value: t.Any,
100 append: bool,
101 language: None | str = None
102 ) -> bool:
103 """
104 Sets the value of the bone to the provided 'value'.
105 Sanity checks are performed; if the value is invalid, the bone value will revert to its original
106 (default) value and the function will return False.
108 :param skel: Dictionary with the current values from the skeleton the bone belongs to
109 :param boneName: The name of the bone that should be modified
110 :param value: The value that should be assigned. Its type depends on the type of the bone
111 :param append: If True, the given value will be appended to the existing bone values instead of
112 replacing them. Only supported on bones with multiple=True
113 :param language: Optional, the language of the value if the bone is language-aware
114 :return: A boolean indicating whether the operation succeeded or not
115 :rtype: bool
116 """
117 if append:
118 raise ValueError(f"append is not possible on {self.type} bones")
120 if language:
121 if not self.languages or language not in self.languages: 121 ↛ 122line 121 didn't jump to line 122 because the condition on line 121 was never true
122 return False
124 skel[boneName][language] = utils.parse.bool(value, conf.bone_boolean_str2true)
125 else:
126 skel[boneName] = utils.parse.bool(value, conf.bone_boolean_str2true)
128 return True
130 def singleValueSerialize(self, value, skel: 'SkeletonInstance', name: str, parentIndexed: bool):
131 """
132 Serializes a single value of the bone for storage in the database.
134 Derived bone classes should overwrite this method to implement their own logic for serializing single
135 values.
136 The serialized value should be suitable for storage in the database.
137 """
138 if value == self.getEmptyValue(): 138 ↛ 141line 138 didn't jump to line 141 because the condition on line 138 was always true
139 # Keep the bones empty, maybe the empty value is explicit set to None
140 return value
141 return utils.parse.bool(value, conf.bone_boolean_str2true)
143 def buildDBFilter(
144 self,
145 name: str,
146 skel: 'viur.core.skeleton.SkeletonInstance',
147 dbFilter: db.Query,
148 rawFilter: dict,
149 prefix: t.Optional[str] = None
150 ) -> db.Query:
151 """
152 Builds a database filter based on the boolean value.
154 :param name: The name of the `BooleanBone` instance.
155 :param skel: The `SkeletonInstance` object representing the data of the current entity.
156 :param dbFilter: The `Query` object representing the current database filter.
157 :param rawFilter: The dictionary representing the raw filter data received from the client.
158 :param prefix: A prefix to be added to the property name in the database filter.
159 :return: The updated `Query` object representing the updated database filter.
160 :rtype: google.cloud.ndb.query.Query
161 """
162 if name in rawFilter:
163 val = utils.parse.bool(rawFilter[name], conf.bone_boolean_str2true)
164 return super().buildDBFilter(name, skel, dbFilter, {name: val}, prefix=prefix)
166 return dbFilter