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
« 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
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
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
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"""
22SelectBoneMultiple = list[SelectBoneValue]
23"""Type alias for values of a multiple SelectBone."""
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}.'
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}.'
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"
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.
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
70 # handle list/tuple as dicts
71 if isinstance(values, (list, tuple)):
72 values = {value: value for value in values}
74 assert isinstance(values, (dict, OrderedDict)) or callable(values)
75 self._values = values
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.
82 :param str item: The attribute name.
83 :return: The value of the specified attribute.
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()
94 # handle list/tuple as dicts
95 if isinstance(values, (list, tuple)):
96 values = {value: value for value in values}
98 assert isinstance(values, (dict, OrderedDict))
100 prefix = self.translation_key_prefix
101 if callable(prefix):
102 prefix = prefix(self)
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 }
112 return values
114 return super().__getattribute__(item)
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
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
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")]
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 }