Skip to content

Commit cf4051b

Browse files
committed
fix: don't compute a bone in a not existing relation
If you have a compute bone in a skel, thats referenced by a RelationalBone (e.g. OrderSkel -> CartNodeSkel in viur-shop) and the referenced skel is deleted (e.g. CartNodeSkel) this skleton cannot longer be `read` here: ``` cloned_skel = skeletonByKind(skel.kindName)() cloned_skel.read(skel["key"]) ``` But here's not handling if the `read` _fails_ and returns `None`, therefore the skel has only the default values and the computing methods fails if it expeect for example a not-`None` `key` in the skel. Since I detected this on rebuild search index, this breaked the entire task and even the default error logging of the skel failed, since it coulnd't be unserialized.
1 parent fa3f977 commit cf4051b

File tree

4 files changed

+41
-4
lines changed

4 files changed

+41
-4
lines changed

src/viur/core/bones/base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1500,8 +1500,10 @@ def _compute(self, skel: 'viur.core.skeleton.SkeletonInstance', bone_name: str):
15001500

15011501
if issubclass(skel.skeletonCls, RefSkel): # we have a ref skel we must load the complete skeleton
15021502
cloned_skel = skeletonByKind(skel.kindName)()
1503-
cloned_skel.read(skel["key"])
1503+
if not cloned_skel.read(skel["key"]):
1504+
raise ValueError(f"{skel["key"]=} does no longer exist. Cannot compute a broken relation")
15041505
else:
1506+
logging.debug(f"Cloning {skel["key"]=}")
15051507
cloned_skel = skel.clone()
15061508
cloned_skel[bone_name] = None # remove value form accessedValues to avoid endless recursion
15071509
compute_fn_args["skel"] = cloned_skel
@@ -1512,6 +1514,7 @@ def _compute(self, skel: 'viur.core.skeleton.SkeletonInstance', bone_name: str):
15121514
if "bone_name" in compute_fn_parameters:
15131515
compute_fn_args["bone_name"] = bone_name
15141516

1517+
# logging.debug(f"{self.compute.fn=} | {compute_fn_args=} | {self=}")
15151518
ret = self.compute.fn(**compute_fn_args)
15161519

15171520
def unserialize_raw_value(raw_value: list[dict] | dict | None):

src/viur/core/bones/relational.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,9 +1028,12 @@ def refresh(self, skel: "SkeletonInstance", name: str) -> None:
10281028
return
10291029

10301030
for _, _, value in self.iter_bone_value(skel, name):
1031+
# logging.debug(f"refresh {self=} {value=}")
10311032
if value and value["dest"]:
10321033
try:
1034+
# logging.debug(f"{value["dest"]=}")
10331035
target_skel = value["dest"].read()
1036+
# logging.debug(f"{target_skel=}")
10341037
except ValueError:
10351038
logging.error(
10361039
f"{name}: The key {value['dest']['key']!r} ({value['dest'].get('name')!r}) seems to be gone"

src/viur/core/skeleton.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def __init__(
180180
bone_map = bone_map or {}
181181

182182
if bones:
183-
names = ("key", ) + tuple(bones)
183+
names = ("key",) + tuple(bones)
184184

185185
# generate full keys sequence based on definition; keeps order of patterns!
186186
keys = []
@@ -1157,7 +1157,7 @@ def read(
11571157
if db_res := db.Get(db_key):
11581158
skel.setEntity(db_res)
11591159
return skel
1160-
elif create in (False, None):
1160+
elif create in (False, None):
11611161
return None
11621162
elif isinstance(create, dict):
11631163
if create and not skel.fromClient(create, amend=True):
@@ -1381,7 +1381,7 @@ def __txn_write(write_skel):
13811381
skel.dbEntity["viur"].setdefault("viurActiveSeoKeys", [])
13821382
for language, seo_key in last_set_seo_keys.items():
13831383
if skel.dbEntity["viur"]["viurCurrentSeoKeys"][language] not in \
1384-
skel.dbEntity["viur"]["viurActiveSeoKeys"]:
1384+
skel.dbEntity["viur"]["viurActiveSeoKeys"]:
13851385
# Ensure the current, active seo key is in the list of all seo keys
13861386
skel.dbEntity["viur"]["viurActiveSeoKeys"].insert(0, seo_key)
13871387
if str(skel.dbEntity.key.id_or_name) not in skel.dbEntity["viur"]["viurActiveSeoKeys"]:
@@ -2017,9 +2017,37 @@ def _run(module: str, notify: str):
20172017
class RebuildSearchIndex(QueryIter):
20182018
@classmethod
20192019
def handleEntry(cls, skel: SkeletonInstance, customData: dict[str, str]):
2020+
print("\n" * 3)
2021+
print("----" * 10)
2022+
print("\n" * 3)
2023+
# logging.debug(f"{skel["key"]=}")
2024+
# logging.debug(f"{skel["key"]=!r}")
2025+
# logging.debug(f"{skel.dbEntity.get("cart")=!r}")
2026+
# logging.debug(f"{skel.dbEntity["cart"]=!r}")
2027+
# logging.debug(f"{skel.dbEntity["cart"]["dest"]=!r}")
2028+
# logging.debug(f"{skel.dbEntity["cart"]["dest"].key=!r}")
2029+
# logging.debug(f"{skel.dbEntity["cart"]["dest"].key=!s}")
2030+
# logging.debug(f"{skel.dbEntity["cart"].key=!r}")
2031+
# logging.debug(f"{skel.dbEntity["cart"].key=!s}")
2032+
# logging.debug(f"{skel=}")
2033+
logging.debug(f"{customData=}")
20202034
skel.refresh()
20212035
skel.write(update_relations=False)
20222036

2037+
@classmethod
2038+
def handleError(cls, skel, customData, exception) -> bool:
2039+
"""
2040+
Handle a error occurred in handleEntry.
2041+
If this function returns True, the queryIter continues, otherwise it breaks and prints the current cursor.
2042+
"""
2043+
logging.exception(f'{cls.__qualname__}.handleEntry failed on skel {skel["key"]=}: {exception}')
2044+
try:
2045+
logging.debug(f"{skel=!r}")
2046+
except Exception: # noqa
2047+
logging.warning("Failed to dump skel")
2048+
logging.debug(f"{skel.dbEntity=}")
2049+
return True
2050+
20232051
@classmethod
20242052
def handleFinish(cls, totalCount: int, customData: dict[str, str]):
20252053
QueryIter.handleFinish(totalCount, customData)

src/viur/core/tasks.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,13 +800,16 @@ def _qryStep(cls, qryDict: dict[str, t.Any]) -> None:
800800
qryIter = qry.run(5)
801801
for item in qryIter:
802802
try:
803+
logging.debug(f"Attempt 1 @ {item["key"]} / {qryDict["totalCount"]}")
803804
cls.handleEntry(item, qryDict["customData"])
804805
except: # First exception - we'll try another time (probably/hopefully transaction collision)
805806
time.sleep(5)
806807
try:
808+
logging.debug(f"Attempt 2 @ {item["key"]} / {qryDict["totalCount"]}")
807809
cls.handleEntry(item, qryDict["customData"])
808810
except Exception as e: # Second exception - call error_handler
809811
try:
812+
logging.debug(f"Attempt 3 @ {item["key"]} / {qryDict["totalCount"]}")
810813
doCont = cls.handleError(item, qryDict["customData"], e)
811814
except Exception as e:
812815
logging.error(f"handleError failed on {item} - bailing out")

0 commit comments

Comments
 (0)