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
« 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
3import fnmatch
4import logging # noqa
5import typing as t
7from .meta import BaseSkeleton
8from .utils import skeletonByKind
9from .. import db, utils
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
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`.
20 It needs to be sub-classed where information about the kindName and its attributes
21 (bones) are specified.
23 The Skeleton stores its bones in an :class:`OrderedDict`-Instance, so the definition order of the
24 contained bones remains constant.
25 """
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
38 def unserialize(self, values: db.Entity | dict):
39 """
40 Loads 'values' into this skeleton.
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()
47 if values:
48 self.dbEntity.update(values)
49 else:
50 self.dbEntity = values
52 self.accessedValues = {}
53 self.renderAccessedValues = {}
56class RefSkel(RelSkel):
57 skeletonCls: t.Optional[t.Type["Skeleton"]] = None
58 """Reference source skeleton class"""
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``.
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
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.
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.
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
95 :raise ValueError: If the entry is no longer in the database.
96 """
97 skel_cls = skeletonByKind(self.kindName)
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()
104 if not skel.read(key or self["key"]):
105 raise ValueError(f"""The key {key or self["key"]!r} seems to be gone""")
107 return skel