Coverage for savcfg/core.py: 54%

1 from types import NoneType

2 from typing import Any

3

4 Primitive = bool | float | int | str | NoneType

5

6

7 class Group(dict):

8 """

9 A dictionary that enforces:

10 - All keys are strings

11 - All values are JSON-serializable (primitives, lists, dicts, or nested JsonableDict)

12 """

13

14 PRIMITIVES = (str, int, float, bool, NoneType)

15

16 def __init__(self, *args, **kwargs):

17 # Create a temporary dict from input

18 temp = dict(*args, **kwargs)

19 # Validate all entries before accepting

20 for key, value in temp.items():

21

22 self._validate_key(key)

23 self._validate_value(value)

24 # Use dict's __init__ after validation

25 super().__init__(temp)

26

27 def _validate_key(self, key: Any) -> None:

28 if not isinstance(key, str):

29 raise TypeError(

30 f"JsonableDict keys must be strings, got {type(key).__name__}: {key! r}"

31 )

32

33 def _validate_value(self, value: Any) -> None:

34 if value is None or isinstance(value, (str, int, float, bool)):

35 return

36

37 if isinstance(value, (list, tuple)):

38 for item in value:

39 self._validate_value(item)

40 return

41

42 if isinstance(value, dict):

43 # Allow regular dicts (will be recursively validated on assignment)

44 for k, v in value.items():

45 self._validate_key(k)

46 self._validate_value(v)

47 return

48

49 if isinstance(value, Group):

50 return # Already validated internally

51

52 raise TypeError(

53 f"Value is not JSON serializable: {value! r} (type: {type(value).__name__})"

54 )

55

56 # Override mutation methods to enforce rules

57

58 def __setitem__(self, key: str, value: Primitive) -> None:

59 self._validate_key(key)

60 self._validate_value(value)

61 super().__setitem__(key, value)

62

63 def update(self, other=(), /, **kwds) -> None:

64 print(other)

65 if hasattr(other, "keys"):

66 for k in other.keys():

67 if type(other[k]) is Group and type(self.get(k)) is Group:

68 self[k].update(other[k])

69 else:

70 self[k] = other[k]

71 else:

72 for k, v in other:

73 if type(self.get(k)) is Group and type(v) is Group:

74 self[k].update(v)

75 else:

76 self[k] = v

77 for k, v in kwds.items():

78 if type(self.get(k)) is Group and type(v) is Group:

79 self[k].update(v)

80 else:

81 self[k] = v

82

83 def setdefault(self, key: str, default: Primitive = None) -> Primitive:

84 if key not in self:

85 self[key] = default

86 return self[key]