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 )