Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/select.py: 23%
67 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-03 12:27 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-03 12:27 +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
9if t.TYPE_CHECKING: 9 ↛ 10line 9 didn't jump to line 10 because the condition on line 9 was never true
10 from viur.core.skeleton import SkeletonInstance
12SelectBoneValue = t.Union[str, Number, enum.Enum]
13"""
14Type alias of possible values in a SelectBone. SelectBoneValue can be either a string (str) or a number (Number)
15"""
17SelectBoneMultiple = list[SelectBoneValue]
18"""Type alias for values of a multiple SelectBone."""
21def translation_key_prefix_skeleton_bonename(bones_instance: BaseBone) -> str:
22 """Generate a translation key prefix based on the skeleton and bone name"""
23 return f'skeleton.{bones_instance.skel_cls.__name__.lower().removesuffix("skel")}.{bones_instance.name}.'
26def translation_key_prefix_bonename(bones_instance: BaseBone) -> str:
27 """Generate a translation key prefix based on the bone name"""
28 return f'bone.{bones_instance.name}.'
31class SelectBone(BaseBone):
32 """
33 A SelectBone is a bone which can take a value from a certain list of values.
34 Inherits from the BaseBone class. The `type` attribute is set to "select".
35 """
36 type = "select"
38 def __init__(
39 self,
40 *,
41 defaultValue: t.Union[
42 SelectBoneValue,
43 SelectBoneMultiple,
44 t.Dict[str, t.Union[SelectBoneMultiple, SelectBoneValue]],
45 t.Callable[["SkeletonInstance", t.Self], t.Any],
46 ] = None,
47 values: dict | list | tuple | t.Callable | enum.EnumMeta = (),
48 translation_key_prefix: str | t.Callable[[t.Self], str] = "",
49 add_missing_translations: bool = False,
50 **kwargs
51 ):
52 """
53 Initializes a new SelectBone.
55 :param defaultValue: key(s) of the values which will be checked by default.
56 :param values: dict of key->value pairs from which the user can choose from
57 -- or a callable that returns a dict.
58 :param translation_key_prefix: A prefix for the key of the translation object.
59 It is empty by default, so that only the label (dict value) from the values is used.
60 A static string or dynamic method can be used (like `translation_key_prefix_bonename`).
61 :param kwargs: Additional keyword arguments that will be passed to the superclass' __init__ method.
62 """
63 super().__init__(defaultValue=defaultValue, **kwargs)
64 self.translation_key_prefix = translation_key_prefix
65 self.add_missing_translations = add_missing_translations
67 # handle list/tuple as dicts
68 if isinstance(values, (list, tuple)):
69 values = {value: value for value in values}
71 assert isinstance(values, (dict, OrderedDict)) or callable(values)
72 self._values = values
74 def __getattribute__(self, item):
75 """
76 Overrides the default __getattribute__ method to handle the 'values' attribute dynamically. If the '_values'
77 attribute is callable, it will be called and the result will be stored in the 'values' attribute.
79 :param str item: The attribute name.
80 :return: The value of the specified attribute.
82 :raises AssertionError: If the resulting values are not of type dict or OrderedDict.
83 """
84 if item == "values":
85 values = self._values
86 if isinstance(values, enum.EnumMeta):
87 values = {value.value: value.name for value in values}
88 elif callable(values):
89 values = values()
91 # handle list/tuple as dicts
92 if isinstance(values, (list, tuple)):
93 values = {value: value for value in values}
95 assert isinstance(values, (dict, OrderedDict))
97 prefix = self.translation_key_prefix
98 if callable(prefix):
99 prefix = prefix(self)
101 values = {
102 key: label if isinstance(label, translate) else translate(
103 f"{prefix}{key}", str(label),
104 f"value {key} for {self.name}<{type(self).__name__}> "
105 + f"in {self.skel_cls.__name__} in {self.skel_cls}",
106 add_missing=self.add_missing_translations,
107 )
108 for key, label in values.items()
109 }
111 return values
113 return super().__getattribute__(item)
115 def singleValueUnserialize(self, val):
116 if isinstance(self._values, enum.EnumMeta):
117 for value in self._values:
118 if value.value == val:
119 return value
120 return val
122 def singleValueSerialize(self, val, skel: 'SkeletonInstance', name: str, parentIndexed: bool):
123 if isinstance(self._values, enum.EnumMeta) and isinstance(val, self._values):
124 return val.value
125 return val
127 def singleValueFromClient(self, value, skel, bone_name, client_data):
128 if isinstance(self._values, enum.EnumMeta) and isinstance(value, self._values):
129 return value, None
131 value = str(value)
132 if not value:
133 return self.getEmptyValue(), [ReadFromClientError(ReadFromClientErrorSeverity.Empty, "No value selected")]
135 for key in self.values.keys():
136 if str(key) == value:
137 if isinstance(self._values, enum.EnumMeta):
138 return self._values(key), None
140 return key, None
142 return self.getEmptyValue(), [
143 ReadFromClientError(ReadFromClientErrorSeverity.Invalid, "Invalid value selected")
144 ]
146 def structure(self) -> dict:
147 return super().structure() | {
148 "values":
149 {k: str(v) for k, v in self.values.items()} # new-style dict
150 if "bone.select.structure.values.keytuple" not in conf.compatibility
151 else [(k, str(v)) for k, v in self.values.items()] # old-style key-tuple
152 }