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: Implement CloneBehavior + CloneStrategy for a bone individual clone behavior #1401

Merged
merged 13 commits into from
Feb 7, 2025
Prev Previous commit
Next Next commit
docs
sveneberth committed Jan 31, 2025
commit 1f57b033c606be7cada6296227b1bd2e05cd43f9
47 changes: 34 additions & 13 deletions src/viur/core/bones/base.py
Original file line number Diff line number Diff line change
@@ -7,17 +7,17 @@

import copy
import dataclasses
import enum
import hashlib
import inspect
import logging
import typing as t
from collections.abc import Iterable
from dataclasses import dataclass, field
from datetime import timedelta
from enum import Enum, auto
import enum
from enum import Enum

from viur.core import db, utils, current, i18n
from viur.core import current, db, i18n, utils
from viur.core.config import conf

if t.TYPE_CHECKING:
@@ -207,30 +207,50 @@


class CloneStrategy(enum.StrEnum):
"""Strategy for selecting the value of a cloned skeleton"""

SET_NULL = enum.auto()
"""Sets the cloned bone value to None."""

SET_DEFAULT = enum.auto()
"""Sets the cloned bone value to its defaultValue."""

SET_EMPTY = enum.auto()
"""Sets the cloned bone value to its emptyValue."""

COPY_VALUE = enum.auto()
"""Copies the bone value from the source skeleton."""

CUSTOM = enum.auto()
"""Uses a custom-defined logic for setting the cloned value.
Requires :attr:`CloneBehavior.custom_func` to be set.
"""


class CloneCustomFunc(t.Protocol):
def __call__(self, skel: "SkeletonInstance", src_skel: "SkeletonInstance", bone_name:str) -> t.Any: ...
"""Type for a custom clone function assigned to :attr:`CloneBehavior.custom_func`"""

def __call__(self, skel: "SkeletonInstance", src_skel: "SkeletonInstance", bone_name: str) -> t.Any: ...


@dataclass
class CloneBehavior:
"""Strategy configuration for selecting the value of a cloned skeleton"""

strategy: CloneStrategy
custom_func: callable = None
"""The strategy used to select a value from a cloned skeleton"""

custom_func: CloneCustomFunc = None
"""custom-defined logic for setting the cloned value
Only required when :attr:`strategy` is set to :attr:`CloneStrategy.CUSTOM`.
"""

def __post_init__(self):
"""Validate this configuration."""
if self.strategy == CloneStrategy.CUSTOM and self.custom_func is None:
raise ValueError("CloneStrategy is CUSTOM, but custom_func is not set")
elif self.strategy != CloneStrategy.CUSTOM and self.custom_func is not None:
raise ValueError("custom_func is set, but CloneStrategy is not CUSTOM")
# if not ((self.strategy == CloneStrategy.CUSTOM) ^ (self.custom_func is None)):
# raise ValueError("custom_func is not defined")





class BaseBone(object):
@@ -406,11 +426,11 @@

self.compute = compute

if clone_behavior is None: # auto choose

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

GitHub Actions / linter (3.12)

E261: at least two spaces before inline comment
if self.unique and self.readOnly:
self.clone_behavior = CloneBehavior(CloneStrategy.SET_DEFAULT)
else:
self.clone_behavior = CloneBehavior(CloneStrategy.COPY_VALUE)

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

GitHub Actions / linter (3.12)

E111: indentation is not a multiple of 4
# TODO: Any different setting for computed bones?
elif isinstance(clone_behavior, CloneStrategy):
self.clone_behavior = CloneBehavior(strategy=clone_behavior)
@@ -1295,18 +1315,19 @@
"""
pass

def clone_value(self, skel: "SkeletonInstance", src_skel:"SkeletonInstance", bone_name: str) -> None:
def clone_value(self, skel: "SkeletonInstance", src_skel: "SkeletonInstance", bone_name: str) -> None:
"""Clone / Set the value for this bone depending on :attr:`clone_behavior`"""
logging.debug(f"{bone_name=} | {self.clone_behavior=}")
match self.clone_behavior.strategy:
case CloneStrategy.COPY_VALUE:
try:
skel.accessedValues[bone_name] = copy.deepcopy(src_skel.accessedValues[bone_name])
except KeyError:
pass # bone_name is not in accessedValues, no need to clone_behavior
pass # bone_name is not in accessedValues, no need to clone_behavior
try:
skel.renderAccessedValues[bone_name] = copy.deepcopy(src_skel.renderAccessedValues[bone_name])
except KeyError:
pass # bone_name is not in renderAccessedValues, no need to clone_behavior
pass # bone_name is not in renderAccessedValues, no need to clone_behavior
case CloneStrategy.SET_NULL:
skel.accessedValues[bone_name] = None
case CloneStrategy.SET_DEFAULT:
2 changes: 1 addition & 1 deletion src/viur/core/prototypes/tree.py
Original file line number Diff line number Diff line change
@@ -727,7 +727,7 @@ def clone(self, skelType: SkelType, key: db.Key | str | int, **kwargs):

# Remember source skel and unset the key for clone operation!
src_skel = skel
skel = skel.clone()
skel = skel.clone(apply_clone_strategy=True)
skel["key"] = None

# make parententry required and writeable when provided
6 changes: 4 additions & 2 deletions src/viur/core/skeleton.py
Original file line number Diff line number Diff line change
@@ -10,9 +10,11 @@
import time
import typing as t
import warnings
from deprecated.sphinx import deprecated
from functools import partial
from itertools import chain

from deprecated.sphinx import deprecated

from viur.core import conf, current, db, email, errors, translate, utils
from viur.core.bones import (
BaseBone,
@@ -399,7 +401,7 @@ def __ior__(self, other: dict | SkeletonInstance | db.Entity) -> SkeletonInstanc
raise ValueError("Unsupported Type")
return self

def clone(self, *, apply_clone_strategy:bool=False) -> t.Self:
def clone(self, *, apply_clone_strategy: bool = False) -> t.Self:
"""
Clones a SkeletonInstance into a modificable, stand-alone instance.
This will also allow to modify the underlying data model.

Unchanged files with check annotations Beta

defaultValue: int | float = lambda *args, **kwargs: time.time(),
descr: str = "SortIndex",
precision: int = 8,
clone_behavior = CloneBehavior(CloneStrategy.SET_DEFAULT),

Check failure on line 28 in src/viur/core/bones/sortindex.py

GitHub Actions / linter (3.12)

E251: unexpected spaces around keyword / parameter equals

Check failure on line 28 in src/viur/core/bones/sortindex.py

GitHub Actions / linter (3.12)

E251: unexpected spaces around keyword / parameter equals
**kwargs
):
super().__init__(