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 TypeVar for Skeleton and make SkeletonInstance Generic #1412

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions src/viur/core/modules/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from viur.core.prototypes.list import List
from viur.core.ratelimit import RateLimit
from viur.core.securityheaders import extendCsp
from viur.core.skeleton import SkeletonInstance


@functools.total_ordering
Expand Down Expand Up @@ -194,7 +195,7 @@ def __init__(self, moduleName, modulePath, userModule):
super().__init__(moduleName, modulePath)
self._user_module = userModule

def can_handle(self, skel: skeleton.SkeletonInstance) -> bool:
def can_handle(self, skel: skeleton.SkeletonInstance[UserSkel]) -> bool:
return True

@classmethod
Expand Down Expand Up @@ -1309,7 +1310,7 @@ def editSkel(self, *args, **kwargs):
def secondFactorProviderByClass(self, cls) -> UserSecondFactorAuthentication:
return getattr(self, f"f2_{cls.__name__.lower()}")

def getCurrentUser(self):
def getCurrentUser(self) -> SkeletonInstance[UserSkel] | None:
session = current.session.get()

req = current.request.get()
Expand Down
35 changes: 19 additions & 16 deletions src/viur/core/skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
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,
Expand All @@ -41,6 +40,8 @@
ABSTRACT_SKEL_CLS_SUFFIX = "AbstractSkel"
KeyType: t.TypeAlias = db.Key | str | int

Skeleton_Cls = t.TypeVar("Skeleton_Cls", bound="BaseSkeleton")


class MetaBaseSkel(type):
"""
Expand Down Expand Up @@ -129,7 +130,7 @@ def __setattr__(self, key, value):
value.__set_name__(self, key)


class SkeletonInstance:
class SkeletonInstance(t.Generic[Skeleton_Cls]):
"""
The actual wrapper around a Skeleton-Class. An object of this class is what's actually returned when you
call a Skeleton-Class. With ViUR3, you don't get an instance of a Skeleton-Class any more - it's always this
Expand All @@ -148,7 +149,7 @@ class SkeletonInstance:

def __init__(
self,
skel_cls: t.Type[Skeleton],
skel_cls: t.Type[Skeleton_Cls],
*,
bones: t.Iterable[str] = (),
bone_map: t.Optional[t.Dict[str, BaseBone]] = None,
Expand Down Expand Up @@ -180,7 +181,7 @@ def __init__(
bone_map = bone_map or {}

if bones:
names = ("key", ) + tuple(bones)
names = ("key",) + tuple(bones)

# generate full keys sequence based on definition; keeps order of patterns!
keys = []
Expand Down Expand Up @@ -218,7 +219,7 @@ def __init__(
self.is_cloned = clone
self.renderAccessedValues = {}
self.renderPreparation = None
self.skeletonCls = skel_cls
self.skeletonCls: t.Type[Skeleton_Cls] = skel_cls

def items(self, yieldBoneValues: bool = False) -> t.Iterable[tuple[str, BaseBone]]:
if yieldBoneValues:
Expand Down Expand Up @@ -447,7 +448,7 @@ def __deepcopy__(self, memodict):
return res


class BaseSkeleton(object, metaclass=MetaBaseSkel):
class BaseSkeleton(metaclass=MetaBaseSkel):
"""
This is a container-object holding information about one database entity.

Expand Down Expand Up @@ -610,7 +611,7 @@ def setBoneValue(
@classmethod
def fromClient(
cls,
skel: SkeletonInstance,
skel: SkeletonInstance[t.Self],
data: dict[str, list[str] | str],
*,
amend: bool = False,
Expand Down Expand Up @@ -690,7 +691,7 @@ def fromClient(
return complete

@classmethod
def refresh(cls, skel: SkeletonInstance):
def refresh(cls, skel: SkeletonInstance[t.Self]):
"""
Refresh the bones current content.

Expand All @@ -706,7 +707,7 @@ def refresh(cls, skel: SkeletonInstance):
_ = skel[key] # Ensure value gets loaded
bone.refresh(skel, key)

def __new__(cls, *args, **kwargs) -> SkeletonInstance:
def __new__(cls, *args, **kwargs) -> SkeletonInstance[t.Self]:
return SkeletonInstance(cls, *args, **kwargs)


Expand Down Expand Up @@ -1157,7 +1158,7 @@ def read(
if db_res := db.Get(db_key):
skel.setEntity(db_res)
return skel
elif create in (False, None):
elif create in (False, None):
return None
elif isinstance(create, dict):
if create and not skel.fromClient(create, amend=True):
Expand Down Expand Up @@ -1378,8 +1379,10 @@ def __txn_write(write_skel):

skel.dbEntity["viur"].setdefault("viurActiveSeoKeys", [])
for language, seo_key in last_set_seo_keys.items():
if skel.dbEntity["viur"]["viurCurrentSeoKeys"][language] not in \
skel.dbEntity["viur"]["viurActiveSeoKeys"]:
if (
skel.dbEntity["viur"]["viurCurrentSeoKeys"][language]
not in skel.dbEntity["viur"]["viurActiveSeoKeys"]
):
# Ensure the current, active seo key is in the list of all seo keys
skel.dbEntity["viur"]["viurActiveSeoKeys"].insert(0, seo_key)
if str(skel.dbEntity.key.id_or_name) not in skel.dbEntity["viur"]["viurActiveSeoKeys"]:
Expand Down Expand Up @@ -1803,7 +1806,7 @@ def read(self, key: t.Optional[db.Key | str | int] = None) -> SkeletonInstance:
return skel


class SkelList(list):
class SkelList(list, t.Generic[Skeleton_Cls]):
"""
This class is used to hold multiple skeletons together with other, commonly used information.

Expand All @@ -1822,12 +1825,12 @@ class SkelList(list):
"renderPreparation",
)

def __init__(self, baseSkel=None):
def __init__(self, baseSkel: SkeletonInstance[Skeleton_Cls] = None):
"""
:param baseSkel: The baseclass for all entries in this list
"""
super(SkelList, self).__init__()
self.baseSkel = baseSkel or {}
super().__init__()
self.baseSkel: SkeletonInstance[Skeleton_Cls] | dict = baseSkel or {}
self.getCursor = lambda: None
self.get_orders = lambda: None
self.renderPreparation = None
Expand Down