Coverage for fake_kubernetes_client/resource_field.py: 38%

74 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2025-07-29 12:31 +0300

1"""FakeResourceField implementation for fake Kubernetes client""" 

2 

3import copy 

4from typing import Any, Iterator, Union 

5 

6 

7class FakeResourceField: 

8 """Fake implementation of kubernetes.dynamic.resource.ResourceField""" 

9 

10 def __init__(self, data: Union[dict[str, Any], None]) -> None: 

11 self._data = data if data is not None else {} 

12 

13 def __getattribute__(self, name: str) -> Any: 

14 # ONLY handle essential internal attributes and methods here 

15 # Let __getattr__ handle all dynamic attribute access 

16 if name.startswith("_") or name in {"to_dict", "keys", "values", "get"}: 

17 return object.__getattribute__(self, name) 

18 

19 # Special case: handle 'items' in __getattribute__ to override method lookup 

20 if name == "items": 

21 try: 

22 data = object.__getattribute__(self, "_data") 

23 if "items" in data: 

24 value = data["items"] 

25 if isinstance(value, list): 

26 return [FakeResourceField(item) if isinstance(item, dict) else item for item in value] 

27 return value 

28 except AttributeError: 

29 pass 

30 # Fall back to the method if no items data 

31 return object.__getattribute__(self, name) 

32 

33 # For all other attributes, let __getattr__ handle it 

34 raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") 

35 

36 def __getattr__(self, name: str) -> Any: 

37 # This is called ONLY when __getattribute__ raises AttributeError 

38 # Handle all dynamic attribute access here 

39 

40 # Direct access to _data without using hasattr (avoids recursion) 

41 try: 

42 data = object.__getattribute__(self, "_data") 

43 except AttributeError: 

44 data = {} 

45 

46 # For resource definition access, return simple values for common attributes 

47 # This ensures compatibility with ocp_resources code that expects strings 

48 if name in ["api_version", "group_version", "kind", "plural", "singular", "group", "version"]: 

49 return data.get(name, "") 

50 

51 # Handle general data access 

52 value = data.get(name) 

53 if value is None: 

54 return FakeResourceField(data={}) 

55 elif isinstance(value, dict): 

56 return FakeResourceField(data=value) 

57 elif isinstance(value, list): 

58 return [FakeResourceField(data=item) if isinstance(item, dict) else item for item in value] 

59 else: 

60 return value 

61 

62 def __getitem__(self, key: str) -> Any: 

63 value = self._data.get(key) 

64 if value is None: 

65 return FakeResourceField(data={}) 

66 elif isinstance(value, dict): 

67 return FakeResourceField(data=value) 

68 elif isinstance(value, list): 

69 return [FakeResourceField(data=item) if isinstance(item, dict) else item for item in value] 

70 else: 

71 return value 

72 

73 def __contains__(self, key: str) -> bool: 

74 return key in self._data 

75 

76 def __bool__(self) -> bool: 

77 return bool(self._data) 

78 

79 def __str__(self) -> str: 

80 return str(self._data) 

81 

82 def __repr__(self) -> str: 

83 return f"FakeResourceField({self._data})" 

84 

85 def get(self, key: str, default: Any = None) -> Any: 

86 value = self._data.get(key, default) 

87 if isinstance(value, dict) and value != default: 

88 return FakeResourceField(data=value) 

89 return value 

90 

91 def to_dict(self) -> dict[str, Any]: 

92 """Convert to dictionary""" 

93 return copy.deepcopy(self._data) 

94 

95 def keys(self) -> Any: 

96 """Get dictionary keys""" 

97 return self._data.keys() 

98 

99 def values(self) -> Any: 

100 """Get dictionary values""" 

101 return self._data.values() 

102 

103 def items(self) -> Any: 

104 """Get dictionary items - but handle special case where 'items' is data""" 

105 # If 'items' key exists in data, return that instead of dict.items() 

106 if "items" in self._data: 

107 value = self._data["items"] 

108 if isinstance(value, list): 

109 return [FakeResourceField(data=item) if isinstance(item, dict) else item for item in value] 

110 return value 

111 # Otherwise return dict.items() 

112 return self._data.items() 

113 

114 def __iter__(self) -> Iterator[str]: 

115 """Make it iterable like a dictionary""" 

116 return iter(self._data) 

117 

118 def __len__(self) -> int: 

119 """Get length like a dictionary""" 

120 return len(self._data)