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
class TerraDataModel:
 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.

TerraDataModel()
def keys(self) -> Generator[str, NoneType, NoneType]:
39    def keys(self) -> typing.Generator[str, None, None]:
40        yield from self._get_attrs()
def values(self) -> Generator[Any, NoneType, NoneType]:
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)
def items(self) -> Generator[Tuple[str, Any], NoneType, NoneType]:
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)
def to_dict(self) -> Dict[str, Any]:
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.

def filter_data( self: terra.models.base_model.TerraDataModel, term: str) -> Generator[~datamodelT, NoneType, NoneType]:
 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]

@classmethod
def from_dict( cls: Type[~datamodelT], model_dict: Dict[str, Any], safe: bool = True) -> ~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

def populate_from_dict( self: ~datamodelT, model_dict: Dict[str, Any], safe: bool = False) -> ~datamodelT:
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