Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/looker_sdk/rtl/model.py: 32%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

133 statements  

1# The MIT License (MIT) 

2# 

3# Copyright (c) 2019 Looker Data Sciences, Inc. 

4# 

5# Permission is hereby granted, free of charge, to any person obtaining a copy 

6# of this software and associated documentation files (the "Software"), to deal 

7# in the Software without restriction, including without limitation the rights 

8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 

9# copies of the Software, and to permit persons to whom the Software is 

10# furnished to do so, subject to the following conditions: 

11# 

12# The above copyright notice and this permission notice shall be included in 

13# all copies or substantial portions of the Software. 

14# 

15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 

21# THE SOFTWARE. 

22 

23"""Base model for all generated models 

24""" 

25 

26import collections 

27import datetime 

28import enum 

29import functools 

30import keyword 

31from typing import Any, Iterable, Optional, Sequence, TypeVar, cast 

32 

33import cattr 

34 

35from looker_sdk.rtl import hooks 

36 

37try: 

38 from typing import ForwardRef # type: ignore 

39except ImportError: 

40 from typing import _ForwardRef as ForwardRef # type: ignore 

41 

42 

43EXPLICIT_NULL = cast(Any, "EXPLICIT_NULL") # type:ignore 

44 

45 

46class Model: 

47 """Base model for all generated models.""" 

48 

49 def _get_converter(self): 

50 if not hasattr(self, "_converter"): 

51 converter = cattr.Converter() 

52 converter.register_unstructure_hook( 

53 datetime.datetime, hooks.datetime_unstructure_hook 

54 ) 

55 uh = functools.partial(hooks.unstructure_hook, converter) 

56 converter.register_unstructure_hook(Model, uh) # type: ignore 

57 self._converter = converter 

58 return self._converter 

59 

60 def _key_to_attr(self, key): 

61 """Appends the trailing _ to python reserved words.""" 

62 if key[-1] == "_": 

63 raise KeyError(key) 

64 if key in keyword.kwlist: 

65 key = f"{key}_" 

66 return key 

67 

68 def __getitem__(self, key): 

69 key = self._key_to_attr(key) 

70 try: 

71 ret = getattr(self, key) 

72 except AttributeError: 

73 raise KeyError(key) 

74 

75 if isinstance(ret, enum.Enum): 

76 ret = ret.value 

77 return ret 

78 

79 def __setitem__(self, key, value): 

80 key = self._key_to_attr(key) 

81 if not hasattr(self, key): 

82 raise AttributeError( 

83 f"'{self.__class__.__name__}' object has no attribute '{key}'" 

84 ) 

85 annotation = self.__annotations__[key] 

86 if isinstance(annotation, ForwardRef): 

87 actual_type = eval( 

88 annotation.__forward_arg__, self.__global_context, locals() 

89 ) 

90 if isinstance(actual_type, enum.EnumMeta): 

91 

92 # untyped because mypy really doesn't like this enum internals stuff 

93 def err(val): 

94 valid = [] 

95 for v in actual_type.__members__.values(): 

96 if v.value != "invalid_api_enum_value": 

97 valid.append(v.value) 

98 return ( 

99 f"Invalid value '{val}' for " # type: ignore 

100 f"'{self.__class__.__name__}.{key}'. Valid values are " 

101 f"{valid}" # type: ignore 

102 ) 

103 

104 if isinstance(value, actual_type): 

105 raise ValueError(err(value)) 

106 enum_member = actual_type(value) 

107 if enum_member.value == "invalid_api_enum_value": 

108 raise ValueError(err(value)) 

109 value = enum_member 

110 elif issubclass(actual_type, Model): 

111 value = self._get_converter().structure(value, actual_type) 

112 

113 return setattr(self, key, value) 

114 

115 def __delitem__(self, key): 

116 self[key] # validates key 

117 setattr(self, self._key_to_attr(key), None) 

118 

119 def __iter__(self): 

120 return iter(self._get_converter().unstructure(self)) 

121 

122 def __len__(self): 

123 return len(self._get_converter().unstructure(self)) 

124 

125 def __contains__(self, key): 

126 return key in self._get_converter().unstructure(self) 

127 

128 def keys(self): 

129 return self._get_converter().unstructure(self).keys() 

130 

131 def items(self): 

132 return self._get_converter().unstructure(self).items() 

133 

134 def values(self): 

135 return self._get_converter().unstructure(self).values() 

136 

137 def get(self, key, default=None): 

138 try: 

139 return self[key] 

140 except KeyError: 

141 return default 

142 

143 def pop(self, key, default=None): 

144 ret = self.get(key, default) 

145 if key in self: 

146 del self[key] 

147 return ret 

148 

149 def popitem(self): 

150 raise NotImplementedError() 

151 

152 def clear(self): 

153 raise NotImplementedError() 

154 

155 def update(self, iterable=None, **kwargs): 

156 if iterable: 

157 has_keys = getattr(iterable, "keys", None) 

158 if callable(has_keys): 

159 for k in iterable: 

160 self[k] = iterable[k] 

161 else: 

162 for k, v in iterable: 

163 self[k] = v 

164 for k in kwargs: 

165 self[k] = kwargs[k] 

166 

167 def setdefault(self, key, default=None): 

168 if key not in self: 

169 self[key] = default 

170 return self[key] 

171 

172 def copy(self): 

173 raise NotImplementedError() 

174 

175 

176def safe_enum__new__(cls, value): 

177 """Handle out-of-spec enum values returned by API. 

178 

179 This is achieved by overriding the __new__ method to return 

180 `invalid_api_enum_value` (defined on each subclass) when an 

181 unexpected value for the enum is returned by the API. 

182 """ 

183 if not isinstance(value, (str, int, bool)): 

184 return super().__new__(cls, value) 

185 else: 

186 vals = {v.value: v for v in cls.__members__.values()} 

187 return vals.get(value, cls.invalid_api_enum_value) 

188 

189 

190T = TypeVar("T") 

191 

192 

193class DelimSequence(collections.UserList, Sequence[T]): 

194 def __init__( 

195 self, 

196 data: Optional[Sequence[T]] = None, 

197 prefix: str = "", 

198 suffix: str = "", 

199 separator: str = ",", 

200 ): 

201 self.prefix = prefix 

202 self.suffix = suffix 

203 self.separator = separator 

204 

205 super().__init__(data) 

206 

207 def append(self, elem: T): 

208 super().append(elem) 

209 

210 def extend(self, iterable: Iterable[T]): 

211 super().extend(iterable) 

212 

213 def insert(self, i: int, elem: T): 

214 super().insert(i, elem) 

215 

216 def remove(self, elem: T): 

217 super().remove(elem) 

218 

219 def index(self, x: T, *args): 

220 super().index(x, *args) # type: ignore 

221 

222 def count(self, elem: T): 

223 super().count(elem) 

224 

225 def __str__(self): 

226 return ( 

227 f"{self.prefix}" 

228 f"{self.separator.join(str(d) for d in self.data)}" 

229 f"{self.suffix}" 

230 )