Coverage for savcfg / core.py: 54%

52 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-10 15:30 +0100

1from types import NoneType 

2from typing import Any 

3 

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

5 

6 

7class 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]