terra.models.base_model
1# Copyright 2022 Terra Enabling Developers Limited 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14from __future__ import annotations 15 16__all__ = ["TerraDataModel"] 17 18import dataclasses 19import enum 20import pydoc 21import typing 22 23PRIMITIVES = (str, int, bool, float, type(None), dict) 24datamodelT = typing.TypeVar("datamodelT", bound="TerraDataModel") 25 26 27class TerraDataModel: 28 """ 29 Base class for all data models that terra returns. 30 """ 31 32 def _get_attrs(self) -> typing.Iterable[str]: 33 return filter( 34 lambda a: not a.startswith("_"), 35 set(dir(self)).difference(set(dir(TerraDataModel))), 36 ) 37 38 def keys(self) -> typing.Generator[str, None, None]: 39 yield from self._get_attrs() 40 41 def values(self) -> typing.Generator[typing.Any, None, None]: 42 attrs = self._get_attrs() 43 yield from (getattr(self, attr) for attr in attrs) 44 45 def items(self) -> typing.Generator[typing.Tuple[str, typing.Any], None, None]: 46 attrs = self._get_attrs() 47 for attr in attrs: 48 yield attr, getattr(self, attr) 49 50 def to_dict(self) -> typing.Dict[str, typing.Any]: 51 """ 52 Get the dictionary (json) representation of the data model. 53 54 This method inspects the attributes of the instance that it is being called on 55 to determine how to build the correct payload from the data stored. 56 57 Returns: 58 :obj:`dict`: Dictionary representation of the data model. 59 """ 60 attrs = self._get_attrs() 61 62 output: typing.Dict[typing.Any, typing.Any] = {} 63 for attr in attrs: 64 attr_val = getattr(self, attr) 65 if isinstance(attr_val, enum.IntEnum): 66 output[attr] = int(attr_val) 67 elif type(attr_val) in PRIMITIVES: 68 output[attr] = attr_val 69 elif isinstance(attr_val, list): 70 if (attr_val and type(attr_val[0]) in PRIMITIVES) or not attr_val: 71 output[attr] = attr_val 72 else: 73 output[attr] = [item.to_dict() for item in attr_val] 74 else: 75 output[attr] = attr_val.to_dict() 76 return output 77 78 def filter_data(self: TerraDataModel, term: str) -> typing.Generator[datamodelT, None, None]: 79 """ 80 Returns a generator of all the data models that match the filter 81 82 Args: 83 term:obj:`str`: the word to filter with 84 85 86 Returns: 87 :obj:`typing.Generator[datamodelT]` 88 """ 89 90 fields_dict = {field.name: field.type for field in dataclasses.fields(self)} 91 # print(fields_dict) 92 93 for field_name, field_type in fields_dict.items(): 94 95 try: 96 97 if isinstance(getattr(self, field_name, None), TerraDataModel): 98 99 for sub_term in field_name.lower().split("_"): 100 if sub_term.lower() == term.lower(): 101 102 yield typing.cast(datamodelT, getattr(self, field_name, None)) 103 # print(getattr(self, field_name , None)) 104 typing.cast(datamodelT, getattr(self, field_name, None)).filter_data(term) 105 106 except Exception as e: 107 108 try: 109 110 for sub_term in field_name.lower().split("_"): 111 if sub_term.lower() == term.lower(): 112 113 yield typing.cast(datamodelT, getattr(self, field_name, None)) 114 115 for inner_item in typing.cast(typing.List[TerraDataModel], getattr(self, field_name, None)): 116 # print(inner_item) 117 inner_item.filter_data(term) 118 119 except Exception as e: 120 # print(traceback.format_exc()) 121 pass 122 123 @classmethod 124 def from_dict( 125 cls: typing.Type[datamodelT], model_dict: typing.Dict[str, typing.Any], safe: bool = True 126 ) -> datamodelT: 127 """ 128 Return the Class data model representation of the dictionary (json). 129 130 This method inspects the attributes of the class that it is being called on 131 to determine how to build the correct payload from the data stored. 132 133 Args: 134 model_dict:obj:`dict`: 135 safe:obj:`bool`: 136 137 Returns: 138 :obj:`terrpython.models.base_model.TerraDataModel` 139 """ 140 # TODO - I don't like this function at all. It can definitely be more elegant 141 data_model = cls() 142 for k, v in model_dict.items(): 143 if ( 144 (inner_item := getattr(data_model, k, *(("NOT_FOUND",) if safe else ()))) in [None, []] 145 or isinstance(inner_item, TerraDataModel) 146 or isinstance(v, list) 147 ): 148 149 if isinstance(inner_item, TerraDataModel): 150 v = inner_item.from_dict(v) 151 152 # if it's a list 153 if isinstance(v, list): 154 155 if v != []: 156 157 # getting all the field types of the current class 158 fields_dict = {field.name: field.type for field in dataclasses.fields(cls())} 159 160 # getting the current field type as a string and removing the 't.optional' 161 current_field_type = str(fields_dict[k]).split("[")[1].split("]")[0] 162 current_field_type2 = str(fields_dict[k]).split("[")[1].split("]")[0] 163 164 # adding terra before the models name 165 if current_field_type.split(".")[0] == "models": 166 current_field_type = "terra." + current_field_type 167 168 if current_field_type2.split(".")[0] == "models": 169 current_field_type2 = "terra-client." + current_field_type2 170 171 # check if the elements of the list are Terra Data Models 172 if current_field_type.split(".")[0] == "terra": 173 174 result = [] 175 176 # for each json object inside the list 177 for inner_dict in v: 178 179 # an instance of a data model of the type of items inside the list 180 inner_data_model = typing.cast( 181 typing.Type[TerraDataModel], pydoc.locate(current_field_type) 182 )() 183 184 # fill up the model 185 inner_data_model = inner_data_model.from_dict(inner_dict) 186 187 # append the model to the result list 188 result.append(inner_data_model) 189 190 v = result 191 192 setattr(data_model, k, v) 193 194 return data_model 195 196 def populate_from_dict( 197 self: datamodelT, model_dict: typing.Dict[str, typing.Any], safe: bool = False 198 ) -> datamodelT: 199 """ 200 Populates missing data fields in the class given a dictionary (json). 201 202 This method inspects the attributes of the instance that it is being called on 203 to determine how to build the correct payload from the data stored. 204 205 Args: 206 model_dict:obj:`dict`: 207 safe:obj:`bool`: 208 209 Returns: 210 :obj:`terrpython.models.base_model.TerraDataModel` 211 """ 212 for k, v in model_dict.items(): 213 if getattr(self, k, *(("NOT_FOUND",) if safe else ())) is None: 214 setattr(self, k, v) 215 216 return self
28class TerraDataModel: 29 """ 30 Base class for all data models that terra returns. 31 """ 32 33 def _get_attrs(self) -> typing.Iterable[str]: 34 return filter( 35 lambda a: not a.startswith("_"), 36 set(dir(self)).difference(set(dir(TerraDataModel))), 37 ) 38 39 def keys(self) -> typing.Generator[str, None, None]: 40 yield from self._get_attrs() 41 42 def values(self) -> typing.Generator[typing.Any, None, None]: 43 attrs = self._get_attrs() 44 yield from (getattr(self, attr) for attr in attrs) 45 46 def items(self) -> typing.Generator[typing.Tuple[str, typing.Any], None, None]: 47 attrs = self._get_attrs() 48 for attr in attrs: 49 yield attr, getattr(self, attr) 50 51 def to_dict(self) -> typing.Dict[str, typing.Any]: 52 """ 53 Get the dictionary (json) representation of the data model. 54 55 This method inspects the attributes of the instance that it is being called on 56 to determine how to build the correct payload from the data stored. 57 58 Returns: 59 :obj:`dict`: Dictionary representation of the data model. 60 """ 61 attrs = self._get_attrs() 62 63 output: typing.Dict[typing.Any, typing.Any] = {} 64 for attr in attrs: 65 attr_val = getattr(self, attr) 66 if isinstance(attr_val, enum.IntEnum): 67 output[attr] = int(attr_val) 68 elif type(attr_val) in PRIMITIVES: 69 output[attr] = attr_val 70 elif isinstance(attr_val, list): 71 if (attr_val and type(attr_val[0]) in PRIMITIVES) or not attr_val: 72 output[attr] = attr_val 73 else: 74 output[attr] = [item.to_dict() for item in attr_val] 75 else: 76 output[attr] = attr_val.to_dict() 77 return output 78 79 def filter_data(self: TerraDataModel, term: str) -> typing.Generator[datamodelT, None, None]: 80 """ 81 Returns a generator of all the data models that match the filter 82 83 Args: 84 term:obj:`str`: the word to filter with 85 86 87 Returns: 88 :obj:`typing.Generator[datamodelT]` 89 """ 90 91 fields_dict = {field.name: field.type for field in dataclasses.fields(self)} 92 # print(fields_dict) 93 94 for field_name, field_type in fields_dict.items(): 95 96 try: 97 98 if isinstance(getattr(self, field_name, None), TerraDataModel): 99 100 for sub_term in field_name.lower().split("_"): 101 if sub_term.lower() == term.lower(): 102 103 yield typing.cast(datamodelT, getattr(self, field_name, None)) 104 # print(getattr(self, field_name , None)) 105 typing.cast(datamodelT, getattr(self, field_name, None)).filter_data(term) 106 107 except Exception as e: 108 109 try: 110 111 for sub_term in field_name.lower().split("_"): 112 if sub_term.lower() == term.lower(): 113 114 yield typing.cast(datamodelT, getattr(self, field_name, None)) 115 116 for inner_item in typing.cast(typing.List[TerraDataModel], getattr(self, field_name, None)): 117 # print(inner_item) 118 inner_item.filter_data(term) 119 120 except Exception as e: 121 # print(traceback.format_exc()) 122 pass 123 124 @classmethod 125 def from_dict( 126 cls: typing.Type[datamodelT], model_dict: typing.Dict[str, typing.Any], safe: bool = True 127 ) -> datamodelT: 128 """ 129 Return the Class data model representation of the dictionary (json). 130 131 This method inspects the attributes of the class that it is being called on 132 to determine how to build the correct payload from the data stored. 133 134 Args: 135 model_dict:obj:`dict`: 136 safe:obj:`bool`: 137 138 Returns: 139 :obj:`terrpython.models.base_model.TerraDataModel` 140 """ 141 # TODO - I don't like this function at all. It can definitely be more elegant 142 data_model = cls() 143 for k, v in model_dict.items(): 144 if ( 145 (inner_item := getattr(data_model, k, *(("NOT_FOUND",) if safe else ()))) in [None, []] 146 or isinstance(inner_item, TerraDataModel) 147 or isinstance(v, list) 148 ): 149 150 if isinstance(inner_item, TerraDataModel): 151 v = inner_item.from_dict(v) 152 153 # if it's a list 154 if isinstance(v, list): 155 156 if v != []: 157 158 # getting all the field types of the current class 159 fields_dict = {field.name: field.type for field in dataclasses.fields(cls())} 160 161 # getting the current field type as a string and removing the 't.optional' 162 current_field_type = str(fields_dict[k]).split("[")[1].split("]")[0] 163 current_field_type2 = str(fields_dict[k]).split("[")[1].split("]")[0] 164 165 # adding terra before the models name 166 if current_field_type.split(".")[0] == "models": 167 current_field_type = "terra." + current_field_type 168 169 if current_field_type2.split(".")[0] == "models": 170 current_field_type2 = "terra-client." + current_field_type2 171 172 # check if the elements of the list are Terra Data Models 173 if current_field_type.split(".")[0] == "terra": 174 175 result = [] 176 177 # for each json object inside the list 178 for inner_dict in v: 179 180 # an instance of a data model of the type of items inside the list 181 inner_data_model = typing.cast( 182 typing.Type[TerraDataModel], pydoc.locate(current_field_type) 183 )() 184 185 # fill up the model 186 inner_data_model = inner_data_model.from_dict(inner_dict) 187 188 # append the model to the result list 189 result.append(inner_data_model) 190 191 v = result 192 193 setattr(data_model, k, v) 194 195 return data_model 196 197 def populate_from_dict( 198 self: datamodelT, model_dict: typing.Dict[str, typing.Any], safe: bool = False 199 ) -> datamodelT: 200 """ 201 Populates missing data fields in the class given a dictionary (json). 202 203 This method inspects the attributes of the instance that it is being called on 204 to determine how to build the correct payload from the data stored. 205 206 Args: 207 model_dict:obj:`dict`: 208 safe:obj:`bool`: 209 210 Returns: 211 :obj:`terrpython.models.base_model.TerraDataModel` 212 """ 213 for k, v in model_dict.items(): 214 if getattr(self, k, *(("NOT_FOUND",) if safe else ())) is None: 215 setattr(self, k, v) 216 217 return self
Base class for all data models that terra returns.
51 def to_dict(self) -> typing.Dict[str, typing.Any]: 52 """ 53 Get the dictionary (json) representation of the data model. 54 55 This method inspects the attributes of the instance that it is being called on 56 to determine how to build the correct payload from the data stored. 57 58 Returns: 59 :obj:`dict`: Dictionary representation of the data model. 60 """ 61 attrs = self._get_attrs() 62 63 output: typing.Dict[typing.Any, typing.Any] = {} 64 for attr in attrs: 65 attr_val = getattr(self, attr) 66 if isinstance(attr_val, enum.IntEnum): 67 output[attr] = int(attr_val) 68 elif type(attr_val) in PRIMITIVES: 69 output[attr] = attr_val 70 elif isinstance(attr_val, list): 71 if (attr_val and type(attr_val[0]) in PRIMITIVES) or not attr_val: 72 output[attr] = attr_val 73 else: 74 output[attr] = [item.to_dict() for item in attr_val] 75 else: 76 output[attr] = attr_val.to_dict() 77 return output
Get the dictionary (json) representation of the data model.
This method inspects the attributes of the instance that it is being called on to determine how to build the correct payload from the data stored.
Returns:
dict
: Dictionary representation of the data model.
79 def filter_data(self: TerraDataModel, term: str) -> typing.Generator[datamodelT, None, None]: 80 """ 81 Returns a generator of all the data models that match the filter 82 83 Args: 84 term:obj:`str`: the word to filter with 85 86 87 Returns: 88 :obj:`typing.Generator[datamodelT]` 89 """ 90 91 fields_dict = {field.name: field.type for field in dataclasses.fields(self)} 92 # print(fields_dict) 93 94 for field_name, field_type in fields_dict.items(): 95 96 try: 97 98 if isinstance(getattr(self, field_name, None), TerraDataModel): 99 100 for sub_term in field_name.lower().split("_"): 101 if sub_term.lower() == term.lower(): 102 103 yield typing.cast(datamodelT, getattr(self, field_name, None)) 104 # print(getattr(self, field_name , None)) 105 typing.cast(datamodelT, getattr(self, field_name, None)).filter_data(term) 106 107 except Exception as e: 108 109 try: 110 111 for sub_term in field_name.lower().split("_"): 112 if sub_term.lower() == term.lower(): 113 114 yield typing.cast(datamodelT, getattr(self, field_name, None)) 115 116 for inner_item in typing.cast(typing.List[TerraDataModel], getattr(self, field_name, None)): 117 # print(inner_item) 118 inner_item.filter_data(term) 119 120 except Exception as e: 121 # print(traceback.format_exc()) 122 pass
Returns a generator of all the data models that match the filter
Args:
termstr
: the word to filter with
Returns:
typing.Generator[datamodelT]
124 @classmethod 125 def from_dict( 126 cls: typing.Type[datamodelT], model_dict: typing.Dict[str, typing.Any], safe: bool = True 127 ) -> datamodelT: 128 """ 129 Return the Class data model representation of the dictionary (json). 130 131 This method inspects the attributes of the class that it is being called on 132 to determine how to build the correct payload from the data stored. 133 134 Args: 135 model_dict:obj:`dict`: 136 safe:obj:`bool`: 137 138 Returns: 139 :obj:`terrpython.models.base_model.TerraDataModel` 140 """ 141 # TODO - I don't like this function at all. It can definitely be more elegant 142 data_model = cls() 143 for k, v in model_dict.items(): 144 if ( 145 (inner_item := getattr(data_model, k, *(("NOT_FOUND",) if safe else ()))) in [None, []] 146 or isinstance(inner_item, TerraDataModel) 147 or isinstance(v, list) 148 ): 149 150 if isinstance(inner_item, TerraDataModel): 151 v = inner_item.from_dict(v) 152 153 # if it's a list 154 if isinstance(v, list): 155 156 if v != []: 157 158 # getting all the field types of the current class 159 fields_dict = {field.name: field.type for field in dataclasses.fields(cls())} 160 161 # getting the current field type as a string and removing the 't.optional' 162 current_field_type = str(fields_dict[k]).split("[")[1].split("]")[0] 163 current_field_type2 = str(fields_dict[k]).split("[")[1].split("]")[0] 164 165 # adding terra before the models name 166 if current_field_type.split(".")[0] == "models": 167 current_field_type = "terra." + current_field_type 168 169 if current_field_type2.split(".")[0] == "models": 170 current_field_type2 = "terra-client." + current_field_type2 171 172 # check if the elements of the list are Terra Data Models 173 if current_field_type.split(".")[0] == "terra": 174 175 result = [] 176 177 # for each json object inside the list 178 for inner_dict in v: 179 180 # an instance of a data model of the type of items inside the list 181 inner_data_model = typing.cast( 182 typing.Type[TerraDataModel], pydoc.locate(current_field_type) 183 )() 184 185 # fill up the model 186 inner_data_model = inner_data_model.from_dict(inner_dict) 187 188 # append the model to the result list 189 result.append(inner_data_model) 190 191 v = result 192 193 setattr(data_model, k, v) 194 195 return data_model
Return the Class data model representation of the dictionary (json).
This method inspects the attributes of the class that it is being called on to determine how to build the correct payload from the data stored.
Args:
model_dictdict
:
safebool
:
Returns:
terrpython.models.base_model.TerraDataModel
197 def populate_from_dict( 198 self: datamodelT, model_dict: typing.Dict[str, typing.Any], safe: bool = False 199 ) -> datamodelT: 200 """ 201 Populates missing data fields in the class given a dictionary (json). 202 203 This method inspects the attributes of the instance that it is being called on 204 to determine how to build the correct payload from the data stored. 205 206 Args: 207 model_dict:obj:`dict`: 208 safe:obj:`bool`: 209 210 Returns: 211 :obj:`terrpython.models.base_model.TerraDataModel` 212 """ 213 for k, v in model_dict.items(): 214 if getattr(self, k, *(("NOT_FOUND",) if safe else ())) is None: 215 setattr(self, k, v) 216 217 return self
Populates missing data fields in the class given a dictionary (json).
This method inspects the attributes of the instance that it is being called on to determine how to build the correct payload from the data stored.
Args:
model_dictdict
:
safebool
:
Returns:
terrpython.models.base_model.TerraDataModel