wallaroo.object

  1import abc
  2from typing import Any, Dict, Optional, Union
  3
  4import gql  # type: ignore
  5
  6
  7class DehydratedValue(object):
  8    """Represents a not-set sentinel value.
  9
 10    Attributes that are null in the database will be returned as None in Python,
 11    and we want them to be set as such, so None cannot be used as a sentinel
 12    value signaling that an optional attribute is not yet set. Objects of this
 13    class fill that role instead.
 14    """
 15
 16    def __init__(self):
 17        pass
 18
 19    def __eq__(self, other: Any):
 20        """All DehydratedValue instances are equal."""
 21        return isinstance(other, DehydratedValue)
 22
 23
 24def rehydrate(attr):
 25    """Decorator that rehydrates the named attribute if needed.
 26
 27    This should decorate getter calls for an attribute:
 28
 29    ```
 30    @rehydrate(_foo_attr)
 31    def foo_attr(self):
 32        return self._foo_attr
 33    ```
 34
 35    This will cause the API object to "rehydrate" (perform a query to fetch and
 36    fill in all attributes from the database) if the named attribute is not set.
 37    """
 38
 39    def decorator(fn):
 40        def wrapper(*args, **kwargs):
 41            obj = args[0]
 42            if not getattr(obj, "_standalone", None):
 43                present = getattr(obj, attr) != DehydratedValue()
 44                # Uncomment to debug while testing
 45                # print(
 46                #    "rehydrate: {} -> {}".format(
 47                #        attr, "present" if present else "not present"
 48                #    )
 49                # )
 50                if not present:
 51                    obj._rehydrate()
 52            result = fn(*args, **kwargs)
 53            return result
 54
 55        return wrapper
 56
 57    return decorator
 58
 59
 60def value_if_present(data: Dict[str, Any], path: str) -> Union[Any, DehydratedValue]:
 61    """Returns a value in a nested dictionary, or DehydratedValue.
 62
 63    :param str path: Dot-delimited path within a nested dictionary; e.g.
 64        `foo.bar.baz`
 65    :return: The requested value inside the dictionary, or DehydratedValue if it
 66        doesn't exist.
 67    """
 68    attrs = path.split(".")
 69    current = data
 70    for attr in attrs:
 71        if attr in current:
 72            current = current[attr]
 73        else:
 74            return DehydratedValue()
 75    return current
 76
 77
 78class RequiredAttributeMissing(Exception):
 79    """Raised when an API object is initialized without a required attribute."""
 80
 81    def __init__(self, class_name: str, attribute_name: str) -> None:
 82        super().__init__(
 83            "{}: Missing required attribute '{}' in response".format(
 84                class_name, attribute_name
 85            )
 86        )
 87
 88
 89class ModelUploadError(Exception):
 90    """Raised when a model file fails to upload."""
 91
 92    def __init__(self, e):
 93        super().__init__("Model failed to upload: {}".format(e))
 94
 95
 96class EntityNotFoundError(Exception):
 97    """Raised when a query for a specific API object returns no results.
 98
 99    This is specifically for queries by unique identifiers that are expected to
100    return exactly one result; queries that can return 0 to many results should
101    return empty list instead of raising this exception.
102    """
103
104    def __init__(self, entity_type: str, params: Dict[str, str]):
105        super().__init__("{} not found: {}".format(entity_type, params))
106
107
108class LimitError(Exception):
109    """Raised when deployment fails."""
110
111    def __init__(self, e):
112        super().__init__(
113            "You have reached a license limit in your Wallaroo instance. In order to add additional resources, you can remove some of your existing resources. If you have any questions contact us at community@wallaroo.ai: {}".format(
114                e
115            )
116        )
117
118
119class DeploymentError(Exception):
120    """Raised when deployment fails."""
121
122    def __init__(self, e):
123        super().__init__("Model failed to deploy: {}".format(e))
124
125
126class InferenceError(Exception):
127    """Raised when inference fails."""
128
129    def __init__(self, error: Dict[str, str]):
130        super().__init__(
131            "Inference failed: \n\t{}: {}".format(error["error_type"], error["error"])
132        ),
133
134
135class InvalidNameError(Exception):
136    """Raised when an entity's name does not meet the expected critieria.
137
138    :param str name: the name string that is invalid
139    :param str req: a string description of the requirement
140    """
141
142    def __init__(self, name: str, req: str):
143        super().__init__(f"Name '{name} is invalid: {req}")
144
145
146class CommunicationError(Exception):
147    """Raised when some component cannot be contacted. There is a networking, configuration or
148    installation problem.
149    """
150
151    def __init__(self, e):
152        super().__init__("Network communication failed: {}".format(e))
153
154
155class Object(abc.ABC):
156    """Base class for all backend GraphQL API objects.
157
158    This class serves as a framework for API objects to be constructed based on
159    a partially-complete JSON response, and to fill in their remaining members
160    dynamically if needed.
161    """
162
163    def __init__(
164        self, gql_client: Optional[gql.Client], data: Dict[str, Any], standalone=False
165    ) -> None:
166        """Base constructor.
167
168        Each object requires:
169        * a GraphQL client - in order to fill its missing members dynamically
170        * an initial data blob - typically from unserialized JSON, contains at
171        * least the data for required
172          members (typically the object's primary key) and optionally other data
173          members.
174        """
175        if gql_client is not None:
176            self._gql_client = gql_client
177        elif gql_client is None and not standalone:
178            raise RuntimeError(
179                "You must either use a client or standalone mode. You cannot mix and match."
180            )
181        self._standalone = standalone
182        # Each object knows how to fill itself from the provided data.
183        self._fill(data)
184
185    def _rehydrate(self):
186        """Fetches all data for this object and fills in all attributes.
187
188        Each object knows how to:
189        * get data for all its attributes via the GraphQL API
190        * fill in those attributes given the unserialized JSON response
191
192        This method chains them together, so that it doesn't have to be done in
193        each object.
194        """
195        self._fill(self._fetch_attributes())
196
197    @abc.abstractmethod
198    def _fill(self, data: Dict[str, Any]) -> None:
199        """Fills an object given a response dictionary from the GraphQL API.
200
201        Only the primary key member must be present; other members will be
202        filled in via rehydration if their corresponding member function is
203        called.
204        """
205        raise NotImplementedError()
206
207    @abc.abstractmethod
208    def _fetch_attributes(self) -> Dict[str, Any]:
209        """Fetches all member data from the GraphQL API."""
210        raise NotImplementedError
class DehydratedValue:
 8class DehydratedValue(object):
 9    """Represents a not-set sentinel value.
10
11    Attributes that are null in the database will be returned as None in Python,
12    and we want them to be set as such, so None cannot be used as a sentinel
13    value signaling that an optional attribute is not yet set. Objects of this
14    class fill that role instead.
15    """
16
17    def __init__(self):
18        pass
19
20    def __eq__(self, other: Any):
21        """All DehydratedValue instances are equal."""
22        return isinstance(other, DehydratedValue)

Represents a not-set sentinel value.

Attributes that are null in the database will be returned as None in Python, and we want them to be set as such, so None cannot be used as a sentinel value signaling that an optional attribute is not yet set. Objects of this class fill that role instead.

DehydratedValue()
17    def __init__(self):
18        pass
def rehydrate(attr):
25def rehydrate(attr):
26    """Decorator that rehydrates the named attribute if needed.
27
28    This should decorate getter calls for an attribute:
29
30    ```
31    @rehydrate(_foo_attr)
32    def foo_attr(self):
33        return self._foo_attr
34    ```
35
36    This will cause the API object to "rehydrate" (perform a query to fetch and
37    fill in all attributes from the database) if the named attribute is not set.
38    """
39
40    def decorator(fn):
41        def wrapper(*args, **kwargs):
42            obj = args[0]
43            if not getattr(obj, "_standalone", None):
44                present = getattr(obj, attr) != DehydratedValue()
45                # Uncomment to debug while testing
46                # print(
47                #    "rehydrate: {} -> {}".format(
48                #        attr, "present" if present else "not present"
49                #    )
50                # )
51                if not present:
52                    obj._rehydrate()
53            result = fn(*args, **kwargs)
54            return result
55
56        return wrapper
57
58    return decorator

Decorator that rehydrates the named attribute if needed.

This should decorate getter calls for an attribute:

@rehydrate(_foo_attr)
def foo_attr(self):
    return self._foo_attr

This will cause the API object to "rehydrate" (perform a query to fetch and fill in all attributes from the database) if the named attribute is not set.

def value_if_present( data: Dict[str, Any], path: str) -> Union[Any, wallaroo.object.DehydratedValue]:
61def value_if_present(data: Dict[str, Any], path: str) -> Union[Any, DehydratedValue]:
62    """Returns a value in a nested dictionary, or DehydratedValue.
63
64    :param str path: Dot-delimited path within a nested dictionary; e.g.
65        `foo.bar.baz`
66    :return: The requested value inside the dictionary, or DehydratedValue if it
67        doesn't exist.
68    """
69    attrs = path.split(".")
70    current = data
71    for attr in attrs:
72        if attr in current:
73            current = current[attr]
74        else:
75            return DehydratedValue()
76    return current

Returns a value in a nested dictionary, or DehydratedValue.

Parameters
  • str path: Dot-delimited path within a nested dictionary; e.g. foo.bar.baz
Returns

The requested value inside the dictionary, or DehydratedValue if it doesn't exist.

class RequiredAttributeMissing(builtins.Exception):
79class RequiredAttributeMissing(Exception):
80    """Raised when an API object is initialized without a required attribute."""
81
82    def __init__(self, class_name: str, attribute_name: str) -> None:
83        super().__init__(
84            "{}: Missing required attribute '{}' in response".format(
85                class_name, attribute_name
86            )
87        )

Raised when an API object is initialized without a required attribute.

RequiredAttributeMissing(class_name: str, attribute_name: str)
82    def __init__(self, class_name: str, attribute_name: str) -> None:
83        super().__init__(
84            "{}: Missing required attribute '{}' in response".format(
85                class_name, attribute_name
86            )
87        )
Inherited Members
builtins.BaseException
with_traceback
args
class ModelUploadError(builtins.Exception):
90class ModelUploadError(Exception):
91    """Raised when a model file fails to upload."""
92
93    def __init__(self, e):
94        super().__init__("Model failed to upload: {}".format(e))

Raised when a model file fails to upload.

ModelUploadError(e)
93    def __init__(self, e):
94        super().__init__("Model failed to upload: {}".format(e))
Inherited Members
builtins.BaseException
with_traceback
args
class EntityNotFoundError(builtins.Exception):
 97class EntityNotFoundError(Exception):
 98    """Raised when a query for a specific API object returns no results.
 99
100    This is specifically for queries by unique identifiers that are expected to
101    return exactly one result; queries that can return 0 to many results should
102    return empty list instead of raising this exception.
103    """
104
105    def __init__(self, entity_type: str, params: Dict[str, str]):
106        super().__init__("{} not found: {}".format(entity_type, params))

Raised when a query for a specific API object returns no results.

This is specifically for queries by unique identifiers that are expected to return exactly one result; queries that can return 0 to many results should return empty list instead of raising this exception.

EntityNotFoundError(entity_type: str, params: Dict[str, str])
105    def __init__(self, entity_type: str, params: Dict[str, str]):
106        super().__init__("{} not found: {}".format(entity_type, params))
Inherited Members
builtins.BaseException
with_traceback
args
class LimitError(builtins.Exception):
109class LimitError(Exception):
110    """Raised when deployment fails."""
111
112    def __init__(self, e):
113        super().__init__(
114            "You have reached a license limit in your Wallaroo instance. In order to add additional resources, you can remove some of your existing resources. If you have any questions contact us at community@wallaroo.ai: {}".format(
115                e
116            )
117        )

Raised when deployment fails.

LimitError(e)
112    def __init__(self, e):
113        super().__init__(
114            "You have reached a license limit in your Wallaroo instance. In order to add additional resources, you can remove some of your existing resources. If you have any questions contact us at community@wallaroo.ai: {}".format(
115                e
116            )
117        )
Inherited Members
builtins.BaseException
with_traceback
args
class DeploymentError(builtins.Exception):
120class DeploymentError(Exception):
121    """Raised when deployment fails."""
122
123    def __init__(self, e):
124        super().__init__("Model failed to deploy: {}".format(e))

Raised when deployment fails.

DeploymentError(e)
123    def __init__(self, e):
124        super().__init__("Model failed to deploy: {}".format(e))
Inherited Members
builtins.BaseException
with_traceback
args
class InferenceError(builtins.Exception):
127class InferenceError(Exception):
128    """Raised when inference fails."""
129
130    def __init__(self, error: Dict[str, str]):
131        super().__init__(
132            "Inference failed: \n\t{}: {}".format(error["error_type"], error["error"])
133        ),

Raised when inference fails.

InferenceError(error: Dict[str, str])
130    def __init__(self, error: Dict[str, str]):
131        super().__init__(
132            "Inference failed: \n\t{}: {}".format(error["error_type"], error["error"])
133        ),
Inherited Members
builtins.BaseException
with_traceback
args
class InvalidNameError(builtins.Exception):
136class InvalidNameError(Exception):
137    """Raised when an entity's name does not meet the expected critieria.
138
139    :param str name: the name string that is invalid
140    :param str req: a string description of the requirement
141    """
142
143    def __init__(self, name: str, req: str):
144        super().__init__(f"Name '{name} is invalid: {req}")

Raised when an entity's name does not meet the expected critieria.

Parameters
  • str name: the name string that is invalid
  • str req: a string description of the requirement
InvalidNameError(name: str, req: str)
143    def __init__(self, name: str, req: str):
144        super().__init__(f"Name '{name} is invalid: {req}")
Inherited Members
builtins.BaseException
with_traceback
args
class CommunicationError(builtins.Exception):
147class CommunicationError(Exception):
148    """Raised when some component cannot be contacted. There is a networking, configuration or
149    installation problem.
150    """
151
152    def __init__(self, e):
153        super().__init__("Network communication failed: {}".format(e))

Raised when some component cannot be contacted. There is a networking, configuration or installation problem.

CommunicationError(e)
152    def __init__(self, e):
153        super().__init__("Network communication failed: {}".format(e))
Inherited Members
builtins.BaseException
with_traceback
args
class Object(abc.ABC):
156class Object(abc.ABC):
157    """Base class for all backend GraphQL API objects.
158
159    This class serves as a framework for API objects to be constructed based on
160    a partially-complete JSON response, and to fill in their remaining members
161    dynamically if needed.
162    """
163
164    def __init__(
165        self, gql_client: Optional[gql.Client], data: Dict[str, Any], standalone=False
166    ) -> None:
167        """Base constructor.
168
169        Each object requires:
170        * a GraphQL client - in order to fill its missing members dynamically
171        * an initial data blob - typically from unserialized JSON, contains at
172        * least the data for required
173          members (typically the object's primary key) and optionally other data
174          members.
175        """
176        if gql_client is not None:
177            self._gql_client = gql_client
178        elif gql_client is None and not standalone:
179            raise RuntimeError(
180                "You must either use a client or standalone mode. You cannot mix and match."
181            )
182        self._standalone = standalone
183        # Each object knows how to fill itself from the provided data.
184        self._fill(data)
185
186    def _rehydrate(self):
187        """Fetches all data for this object and fills in all attributes.
188
189        Each object knows how to:
190        * get data for all its attributes via the GraphQL API
191        * fill in those attributes given the unserialized JSON response
192
193        This method chains them together, so that it doesn't have to be done in
194        each object.
195        """
196        self._fill(self._fetch_attributes())
197
198    @abc.abstractmethod
199    def _fill(self, data: Dict[str, Any]) -> None:
200        """Fills an object given a response dictionary from the GraphQL API.
201
202        Only the primary key member must be present; other members will be
203        filled in via rehydration if their corresponding member function is
204        called.
205        """
206        raise NotImplementedError()
207
208    @abc.abstractmethod
209    def _fetch_attributes(self) -> Dict[str, Any]:
210        """Fetches all member data from the GraphQL API."""
211        raise NotImplementedError

Base class for all backend GraphQL API objects.

This class serves as a framework for API objects to be constructed based on a partially-complete JSON response, and to fill in their remaining members dynamically if needed.

Object( gql_client: Optional[gql.client.Client], data: Dict[str, Any], standalone=False)
164    def __init__(
165        self, gql_client: Optional[gql.Client], data: Dict[str, Any], standalone=False
166    ) -> None:
167        """Base constructor.
168
169        Each object requires:
170        * a GraphQL client - in order to fill its missing members dynamically
171        * an initial data blob - typically from unserialized JSON, contains at
172        * least the data for required
173          members (typically the object's primary key) and optionally other data
174          members.
175        """
176        if gql_client is not None:
177            self._gql_client = gql_client
178        elif gql_client is None and not standalone:
179            raise RuntimeError(
180                "You must either use a client or standalone mode. You cannot mix and match."
181            )
182        self._standalone = standalone
183        # Each object knows how to fill itself from the provided data.
184        self._fill(data)

Base constructor.

Each object requires:

  • a GraphQL client - in order to fill its missing members dynamically
  • an initial data blob - typically from unserialized JSON, contains at
  • least the data for required members (typically the object's primary key) and optionally other data members.