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

feat: Add serialize_compute and unserialize_compute to BaseBone #1145

Merged
151 changes: 85 additions & 66 deletions src/viur/core/bones/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,29 +745,7 @@
:param parentIndexed: A boolean indicating whether the parent bone is indexed.
:return: A boolean indicating whether the serialization was successful.
"""
# Handle compute on write
if self.compute:
match self.compute.interval.method:
case ComputeMethod.OnWrite:
skel.accessedValues[name] = self._compute(skel, name)

case ComputeMethod.Lifetime:
now = utils.utcNow()

last_update = \
skel.accessedValues.get(f"_viur_compute_{name}_") \
or skel.dbEntity.get(f"_viur_compute_{name}_")

if not last_update or last_update + self.compute.interval.lifetime < now:
skel.accessedValues[name] = self._compute(skel, name)
skel.dbEntity[f"_viur_compute_{name}_"] = now

case ComputeMethod.Once:
if name not in skel.dbEntity:
skel.accessedValues[name] = self._compute(skel, name)

# logging.debug(f"WRITE {name=} {skel.accessedValues=}")
# logging.debug(f"WRITE {name=} {skel.dbEntity=}")
self.serialize_compute(skel, name)

if name in skel.accessedValues:
newVal = skel.accessedValues[name]
Expand Down Expand Up @@ -811,6 +789,36 @@
return True
return False

def serialize_compute(self, skel: "SkeletonInstance", name: str) -> None:
"""
This function checks whether a bone is computed and if this is the case, it attempts to serialize the
value with the appropriate calculation method

:param skel : The SkeletonInstance where the current Bone is located
:param name: The name of the Bone in the Skeleton
ArneGudermann marked this conversation as resolved.
Show resolved Hide resolved
"""
if not self.compute:
return None
match self.compute.interval.method:
case ComputeMethod.OnWrite:
skel.accessedValues[name] = self._compute(skel, name)

case ComputeMethod.Lifetime:
now = utils.utcNow()

last_update = \
skel.accessedValues.get(f"_viur_compute_{name}_") \
or skel.dbEntity.get(f"_viur_compute_{name}_")

if not last_update or last_update + self.compute.interval.lifetime < now:
skel.accessedValues[name] = self._compute(skel, name)
skel.dbEntity[f"_viur_compute_{name}_"] = now

case ComputeMethod.Once:
if name not in skel.dbEntity:
skel.accessedValues[name] = self._compute(skel, name)


def singleValueUnserialize(self, val):
"""
Unserializes a single value of the bone from the stored database value.
Expand Down Expand Up @@ -845,49 +853,8 @@
skel.accessedValues[name] = self.getDefaultValue(skel)
return False

# Is this value computed?
# In this case, check for configured compute method and if recomputation is required.
# Otherwise, the value from the DB is used as is.
if self.compute and not self._prevent_compute:
match self.compute.interval.method:
# Computation is bound to a lifetime?
case ComputeMethod.Lifetime:
now = utils.utcNow()

# check if lifetime exceeded
last_update = skel.dbEntity.get(f"_viur_compute_{name}_")
skel.accessedValues[f"_viur_compute_{name}_"] = last_update or now

# logging.debug(f"READ {name=} {skel.dbEntity=}")
# logging.debug(f"READ {name=} {skel.accessedValues=}")

if not last_update or last_update + self.compute.interval.lifetime <= now:
# if so, recompute and refresh updated value
skel.accessedValues[name] = value = self._compute(skel, name)

def transact():
db_obj = db.Get(skel["key"])
db_obj[f"_viur_compute_{name}_"] = now
db_obj[name] = value
db.Put(db_obj)

if db.IsInTransaction():
transact()
else:
db.RunInTransaction(transact)

return True

# Compute on every deserialization
case ComputeMethod.Always:
skel.accessedValues[name] = self._compute(skel, name)
return True

# Only compute once when loaded value is empty
case ComputeMethod.Once:
if loadVal is None:
skel.accessedValues[name] = self._compute(skel, name)
return True
if self.unserialize_compute(skel, name, loadVal):
return True

# unserialize value to given config
if self.languages and self.multiple:
Expand Down Expand Up @@ -965,6 +932,58 @@
skel.accessedValues[name] = res
return True

def unserialize_compute(self, skel: "SkeletonInstance", name: str, loaded_value: t.Any) -> bool:
"""
This function checks whether a bone is computed and if this is the case, it attempts to deserialise the
value with the appropriate calculation method

:param skel : The SkeletonInstance where the current Bone is located
:param name: The name of the Bone in the Skeleton
:param loaded_value: The value from the DB Entity
:return: True if the Bone was unserialized, False otherwise
"""
if not self.compute and self._prevent_compute:
ArneGudermann marked this conversation as resolved.
Show resolved Hide resolved
return False

match self.compute.interval.method:
# Computation is bound to a lifetime?
case ComputeMethod.Lifetime:
now = utils.utcNow()

# check if lifetime exceeded
last_update = skel.dbEntity.get(f"_viur_compute_{name}_")
skel.accessedValues[f"_viur_compute_{name}_"] = last_update or now


phorward marked this conversation as resolved.
Show resolved Hide resolved
if not last_update or last_update + self.compute.interval.lifetime <= now:

Check failure on line 958 in src/viur/core/bones/base.py

View workflow job for this annotation

GitHub Actions / linter (3.12)

E303: too many blank lines (2)
# if so, recompute and refresh updated value
skel.accessedValues[name] = value = self._compute(skel, name)

def transact():
db_obj = db.Get(skel["key"])
db_obj[f"_viur_compute_{name}_"] = now
db_obj[name] = value
db.Put(db_obj)

if db.IsInTransaction():
transact()
else:
db.RunInTransaction(transact)

return True

# Compute on every deserialization
case ComputeMethod.Always:
skel.accessedValues[name] = self._compute(skel, name)
return True

# Only compute once when loaded value is empty
case ComputeMethod.Once:
if loaded_value is None:
skel.accessedValues[name] = self._compute(skel, name)
return True
return False

def delete(self, skel: 'viur.core.skeleton.SkeletonInstance', name: str):
"""
Like postDeletedHandler, but runs inside the transaction
Expand Down
1 change: 1 addition & 0 deletions src/viur/core/bones/credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def serialize(self, skel: 'SkeletonInstance', name: str, parentIndexed: bool) ->
:return: True if the value was updated in the database, False otherwise.
:rtype: bool
"""
self.serialize_compute(skel, name)
skel.dbEntity.exclude_from_indexes.add(name) # Ensure we are never indexed
if name in skel.accessedValues and skel.accessedValues[name]:
skel.dbEntity[name] = skel.accessedValues[name]
Expand Down
2 changes: 0 additions & 2 deletions src/viur/core/bones/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,11 @@ def unserialize(self, skel: 'SkeletonInstance', name: str) -> bool:
):
skel.accessedValues[name] = skel.dbEntity.key
return True

return super().unserialize(skel, name)

def serialize(self, skel: 'SkeletonInstance', name: str, parentIndexed: bool) -> bool:
if name not in skel.accessedValues:
return False

if name == "key":
skel.dbEntity.key = skel.accessedValues["key"]
return True
Expand Down
1 change: 1 addition & 0 deletions src/viur/core/bones/password.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def serialize(self, skel: 'SkeletonInstance', name: str, parentIndexed: bool) ->
otherwise, a list of ReadFromClientErrors containing detailed information about the errors.
:rtype: Union[None, List[ReadFromClientError]]
"""
self.serialize_compute(skel, name)
if not (value := skel.accessedValues.get(name)):
return False

Expand Down
Loading