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
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
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.
23"""Base model for all generated models
24"""
26import collections
27import datetime
28import enum
29import functools
30import keyword
31from typing import Any, Iterable, Optional, Sequence, TypeVar, cast
33import cattr
35from looker_sdk.rtl import hooks
37try:
38 from typing import ForwardRef # type: ignore
39except ImportError:
40 from typing import _ForwardRef as ForwardRef # type: ignore
43EXPLICIT_NULL = cast(Any, "EXPLICIT_NULL") # type:ignore
46class Model:
47 """Base model for all generated models."""
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
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
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)
75 if isinstance(ret, enum.Enum):
76 ret = ret.value
77 return ret
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):
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 )
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)
113 return setattr(self, key, value)
115 def __delitem__(self, key):
116 self[key] # validates key
117 setattr(self, self._key_to_attr(key), None)
119 def __iter__(self):
120 return iter(self._get_converter().unstructure(self))
122 def __len__(self):
123 return len(self._get_converter().unstructure(self))
125 def __contains__(self, key):
126 return key in self._get_converter().unstructure(self)
128 def keys(self):
129 return self._get_converter().unstructure(self).keys()
131 def items(self):
132 return self._get_converter().unstructure(self).items()
134 def values(self):
135 return self._get_converter().unstructure(self).values()
137 def get(self, key, default=None):
138 try:
139 return self[key]
140 except KeyError:
141 return default
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
149 def popitem(self):
150 raise NotImplementedError()
152 def clear(self):
153 raise NotImplementedError()
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]
167 def setdefault(self, key, default=None):
168 if key not in self:
169 self[key] = default
170 return self[key]
172 def copy(self):
173 raise NotImplementedError()
176def safe_enum__new__(cls, value):
177 """Handle out-of-spec enum values returned by API.
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)
190T = TypeVar("T")
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
205 super().__init__(data)
207 def append(self, elem: T):
208 super().append(elem)
210 def extend(self, iterable: Iterable[T]):
211 super().extend(iterable)
213 def insert(self, i: int, elem: T):
214 super().insert(i, elem)
216 def remove(self, elem: T):
217 super().remove(elem)
219 def index(self, x: T, *args):
220 super().index(x, *args) # type: ignore
222 def count(self, elem: T):
223 super().count(elem)
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 )