Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/skeleton/relskel.py: 24%

40 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-13 11:04 +0000

1from __future__ import annotations # noqa: required for pre-defined annotations 

2 

3import typing as t 

4import fnmatch 

5 

6from .. import db, utils 

7from .meta import BaseSkeleton 

8from .utils import skeletonByKind 

9 

10 

11class RelSkel(BaseSkeleton): 

12 """ 

13 This is a Skeleton-like class that acts as a container for Skeletons used as a 

14 additional information data skeleton for :class:`~viur.core.bones.relational.RelationalBone`. 

15 

16 It needs to be sub-classed where information about the kindName and its attributes 

17 (bones) are specified. 

18 

19 The Skeleton stores its bones in an :class:`OrderedDict`-Instance, so the definition order of the 

20 contained bones remains constant. 

21 """ 

22 

23 def serialize(self, parentIndexed): 

24 if self.dbEntity is None: 

25 self.dbEntity = db.Entity() 

26 for key, _bone in self.items(): 

27 # if key in self.accessedValues: 

28 _bone.serialize(self, key, parentIndexed) 

29 # if "key" in self: # Write the key seperatly, as the base-bone doesn't store it 

30 # dbObj["key"] = self["key"] 

31 # FIXME: is this a good idea? Any other way to ensure only bones present in refKeys are serialized? 

32 return self.dbEntity 

33 

34 def unserialize(self, values: db.Entity | dict): 

35 """ 

36 Loads 'values' into this skeleton. 

37 

38 :param values: dict with values we'll assign to our bones 

39 """ 

40 if not isinstance(values, db.Entity): 

41 self.dbEntity = db.Entity() 

42 

43 if values: 

44 self.dbEntity.update(values) 

45 else: 

46 self.dbEntity = values 

47 

48 self.accessedValues = {} 

49 self.renderAccessedValues = {} 

50 

51 

52class RefSkel(RelSkel): 

53 @classmethod 

54 def fromSkel(cls, kindName: str, *args: list[str]) -> t.Type[RefSkel]: 

55 """ 

56 Creates a ``RefSkel`` from a skeleton-class using only the bones explicitly named in ``args``. 

57 

58 :param args: List of bone names we'll adapt 

59 :return: A new instance of RefSkel 

60 """ 

61 newClass = type("RefSkelFor" + kindName, (RefSkel,), {}) 

62 fromSkel = skeletonByKind(kindName) 

63 newClass.kindName = kindName 

64 bone_map = {} 

65 for arg in args: 

66 bone_map |= {k: fromSkel.__boneMap__[k] for k in fnmatch.filter(fromSkel.__boneMap__.keys(), arg)} 

67 newClass.__boneMap__ = bone_map 

68 return newClass 

69 

70 def read( 

71 self, 

72 key: t.Optional[db.Key | str | int] = None, 

73 *, 

74 subskel: t.Iterable[str] = (), 

75 bones: t.Iterable[str] = (), 

76 ) -> "SkeletonInstance": 

77 """ 

78 Read full skeleton instance referenced by the RefSkel from the database. 

79 

80 Can be used for reading the full Skeleton from a RefSkel. 

81 The `key` parameter also allows to read another, given key from the related kind. 

82 

83 :param key: Can be used to overwrite the key; Ohterwise, the RefSkel's key-property will be used. 

84 :param subskel: Optionally form skel from subskels 

85 :param bones: Optionally create skeleton only from the specified bones 

86 

87 :raise ValueError: If the entry is no longer in the database. 

88 """ 

89 skel_cls = skeletonByKind(self.kindName) 

90 

91 if subskel or bones: 

92 skel = skel_cls.subskel(*utils.ensure_iterable(subskel), bones=utils.ensure_iterable(bones)) 

93 else: 

94 skel = skel_cls() 

95 

96 if not skel.read(key or self["key"]): 

97 raise ValueError(f"""The key {key or self["key"]!r} seems to be gone""") 

98 

99 return skel