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

46 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-25 14:23 +0000

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

2 

3import fnmatch 

4import logging # noqa 

5import typing as t 

6 

7from .meta import BaseSkeleton 

8from .utils import skeletonByKind 

9from .. import db, utils 

10 

11if t.TYPE_CHECKING: 11 ↛ 12line 11 didn't jump to line 12 because the condition on line 11 was never true

12 from . import Skeleton 

13 

14 

15class RelSkel(BaseSkeleton): 

16 """ 

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

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

19 

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

21 (bones) are specified. 

22 

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

24 contained bones remains constant. 

25 """ 

26 

27 def serialize(self, parentIndexed): 

28 if self.dbEntity is None: 

29 self.dbEntity = db.Entity() 

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

31 # if key in self.accessedValues: 

32 _bone.serialize(self, key, parentIndexed) 

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

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

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

36 return self.dbEntity 

37 

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

39 """ 

40 Loads 'values' into this skeleton. 

41 

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

43 """ 

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

45 self.dbEntity = db.Entity() 

46 

47 if values: 

48 self.dbEntity.update(values) 

49 else: 

50 self.dbEntity = values 

51 

52 self.accessedValues = {} 

53 self.renderAccessedValues = {} 

54 

55 

56class RefSkel(RelSkel): 

57 skeletonCls: t.Optional[t.Type["Skeleton"]] = None 

58 """Reference source skeleton class""" 

59 

60 @classmethod 

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

62 """ 

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

64 

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

66 :return: A new instance of RefSkel 

67 """ 

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

69 fromSkel = skeletonByKind(kindName) 

70 newClass.kindName = kindName 

71 newClass.skeletonCls = fromSkel 

72 bone_map = {} 

73 for arg in args: 

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

75 newClass.__boneMap__ = bone_map 

76 return newClass 

77 

78 def read( 

79 self, 

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

81 *, 

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

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

84 ) -> "SkeletonInstance": 

85 """ 

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

87 

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

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

90 

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

92 :param subskel: Optionally form skel from subskels 

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

94 

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

96 """ 

97 skel_cls = skeletonByKind(self.kindName) 

98 

99 if subskel or bones: 

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

101 else: 

102 skel = skel_cls() 

103 

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

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

106 

107 return skel