Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Move JSON-serializable rendering into the bones #1000

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/viur/core/bones/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,7 @@ def _hashValueForUniquePropertyIndex(self, value: Union[str, int]) -> List[str]:
:return: A list containing a string representation of the hashed value. If the bone is multiple,
the list may contain more than one hashed value.
"""

def hashValue(value: Union[str, int]) -> str:
h = hashlib.sha256()
h.update(str(value).encode("UTF-8"))
Expand Down Expand Up @@ -1260,3 +1261,29 @@ def structure(self) -> dict:
ret["compute"]["lifetime"] = self.compute.interval.lifetime.total_seconds()

return ret

def render_value(self, skel: "SkeletonInstance", bone_name: str):
ret = {}
bone_value = skel[bone_name]
if self.languages and self.multiple:
res = {}
for language in self.languages:
if bone_value and language in bone_value and bone_value[language]:
ret[language] = [self.render_single_value(value) for value in bone_value[language]]
else:
res[language] = []
elif self.languages:
for language in self.languages:
if bone_value and language in bone_value and bone_value[language]:
ret[language] = self.render_single_value(bone_value[language])
else:
ret[language] = None
elif self.multiple:
ret = [self.render_single_value(value) for value in bone_value]

else:
ret = self.render_single_value(bone_value)
return ret

def render_single_value(self, value):
return value
11 changes: 10 additions & 1 deletion src/viur/core/bones/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from hashlib import sha256
from time import time
from typing import Any, Dict, List, Set, Union
from viur.core import conf, db
from viur.core import conf, db, utils
from viur.core.bones.treeleaf import TreeLeafBone
from viur.core.tasks import CallDeferred

Expand Down Expand Up @@ -306,3 +306,12 @@ def structure(self) -> dict:
return super().structure() | {
"valid_mime_types": self.validMimeTypes
}

def render_single_value(self, value: dict[str, "SkeletonInstance"]) -> dict | None:
res = super().render_single_value(value)
if res is not None:
for key, value in res.items():
if value is not None:
res[key]["downloadUrl"] = utils.downloadUrlFor(value["dlkey"], value["name"], derived=False,
expires=conf.render_json_download_url_expiration)
return res
3 changes: 3 additions & 0 deletions src/viur/core/bones/password.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,6 @@ def structure(self) -> dict:
"tests": self.tests,
"test_threshold": self.test_threshold,
}

def render_single_value(self, value):
return ""
4 changes: 4 additions & 0 deletions src/viur/core/bones/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,7 @@ def structure(self) -> dict:
return super().structure() | {
"format": self.format,
"using": self.using().structure()}

def render_single_value(self, value: "SkeletonInstance") -> dict | None:
if value is not None:
return value.render_bone_values()
7 changes: 7 additions & 0 deletions src/viur/core/bones/relational.py
Original file line number Diff line number Diff line change
Expand Up @@ -1331,3 +1331,10 @@ def structure(self) -> dict:
"using": self.using().structure() if self.using else None,
"relskel": self._refSkelCache().structure(),
}

def render_single_value(self, value: dict[str, "SkeletonInstance"]) -> dict | None:
if isinstance(value, dict):
return {
"dest": value["dest"].render_bone_values(),
phorward marked this conversation as resolved.
Show resolved Hide resolved
"rel": value["rel"].render_bone_values() if value["rel"] else None,
}
103 changes: 19 additions & 84 deletions src/viur/core/render/json/default.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import json
import warnings
from enum import Enum

from viur.core import bones, utils, db, current
from viur.core.skeleton import SkeletonInstance
from viur.core import db, current, logging
from viur.core.skeleton import SkelList, SkeletonInstance
from viur.core.i18n import translate
from viur.core.config import conf
from datetime import datetime
Expand Down Expand Up @@ -68,88 +68,17 @@ def render_structure(structure: dict):

return structure


def renderSingleBoneValue(self, value: Any,
bone: bones.BaseBone,
skel: SkeletonInstance,
key
) -> Union[Dict, str, None]:
"""
Renders the value of a bone.

It can be overridden and super-called from a custom renderer.

:param bone: The bone which value should be rendered.
:type bone: Any bone that inherits from :class:`server.bones.base.BaseBone`.

:return: A dict containing the rendered attributes.
"""
if isinstance(bone, bones.RelationalBone):
if isinstance(value, dict):
return {
"dest": self.renderSkelValues(value["dest"], injectDownloadURL=isinstance(bone, bones.FileBone)),
"rel": (self.renderSkelValues(value["rel"], injectDownloadURL=isinstance(bone, bones.FileBone))
if value["rel"] else None),
}
elif isinstance(bone, bones.RecordBone):
return self.renderSkelValues(value)
elif isinstance(bone, bones.PasswordBone):
return ""
else:
return value
return None

def renderBoneValue(self, bone: bones.BaseBone, skel: SkeletonInstance, key: str) -> Union[List, Dict, None]:
boneVal = skel[key]
if bone.languages and bone.multiple:
res = {}
for language in bone.languages:
if boneVal and language in boneVal and boneVal[language]:
res[language] = [self.renderSingleBoneValue(v, bone, skel, key) for v in boneVal[language]]
else:
res[language] = []
elif bone.languages:
res = {}
for language in bone.languages:
if boneVal and language in boneVal and boneVal[language]:
res[language] = self.renderSingleBoneValue(boneVal[language], bone, skel, key)
else:
res[language] = None
elif bone.multiple:
res = [self.renderSingleBoneValue(v, bone, skel, key) for v in boneVal] if boneVal else None
else:
res = self.renderSingleBoneValue(boneVal, bone, skel, key)
return res

def renderSkelValues(self, skel: SkeletonInstance, injectDownloadURL: bool = False) -> Optional[Dict]:
"""
Prepares values of one :class:`viur.core.skeleton.Skeleton` or a list of skeletons for output.

:param skel: Skeleton which contents will be processed.
"""
if skel is None:
return None
elif isinstance(skel, dict):
return skel
res = {}
for key, bone in skel.items():
res[key] = self.renderBoneValue(bone, skel, key)
if injectDownloadURL and "dlkey" in skel and "name" in skel:
res["downloadUrl"] = utils.downloadUrlFor(skel["dlkey"], skel["name"], derived=False,
expires=conf.render_json_download_url_expiration)
return res

def renderEntry(self, skel: SkeletonInstance, actionName, params=None):
def renderEntry(self, skel: SkeletonInstance | list[SkeletonInstance], actionName, params=None):
structure = None
errors = None

if isinstance(skel, list):
vals = [self.renderSkelValues(x) for x in skel]
vals = [_skel.render_bone_values() for _skel in skel]
if isinstance(skel[0], SkeletonInstance):
structure = DefaultRender.render_structure(skel[0].structure())

elif isinstance(skel, SkeletonInstance):
vals = self.renderSkelValues(skel)
vals = skel.render_bone_values()
structure = DefaultRender.render_structure(skel.structure())
errors = [{"severity": x.severity.value, "fieldPath": x.fieldPath, "errorMessage": x.errorMessage,
"invalidatedFields": x.invalidatedFields} for x in skel.errors]
Expand All @@ -171,21 +100,27 @@ def renderEntry(self, skel: SkeletonInstance, actionName, params=None):
def view(self, skel: SkeletonInstance, action: str = "view", params=None, **kwargs):
return self.renderEntry(skel, action, params)

def list(self, skellist, action: str = "list", params=None, **kwargs):
def list(self, skel_list: SkelList, action: str = "list", params=None, **kwargs):
if "skellist" in kwargs:
msg = f"parameter skellist in the 'list' method of the JSON Renderer is deprecated. " \
f"Please use skel_list as the new parameter instead"
logging.warning(msg, stacklevel=3)
warnings.warn(msg, DeprecationWarning, stacklevel=3)
skel_list = kwargs["skellist"]
phorward marked this conversation as resolved.
Show resolved Hide resolved
# Rendering the structure in lists is flagged as deprecated
structure = None
cursor = None
orders = None

if skellist:
if isinstance(skellist[0], SkeletonInstance):
if skel_list:
if isinstance(skel_list[0], SkeletonInstance):
if "json.bone.structure.inlists" in conf.compatibility:
structure = DefaultRender.render_structure(skellist[0].structure())
structure = DefaultRender.render_structure(skel_list[0].structure())

cursor = skellist.getCursor()
orders = skellist.get_orders()
cursor = skel_list.getCursor()
orders = skel_list.get_orders()

skellist = [self.renderSkelValues(skel) for skel in skellist]
skellist = [skel.render_bone_values() for skel in skel_list]
else:
skellist = []

Expand Down
5 changes: 5 additions & 0 deletions src/viur/core/skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ def structure(self) -> dict:
for i, (key, bone) in enumerate(self.items())
}

def render_bone_values(self):
return {
bone_name: bone.render_value(self, bone_name) for bone_name, bone in self.items()
}

def __deepcopy__(self, memodict):
res = self.clone()
memodict[id(self)] = res
Expand Down