clarifai-grpc 6.4.0__py3-none-any.whl → 11.10.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. clarifai_grpc/__init__.py +33 -0
  2. clarifai_grpc/channel/clarifai_channel.py +150 -44
  3. clarifai_grpc/channel/custom_converters/custom_dict_to_message.py +41 -34
  4. clarifai_grpc/channel/custom_converters/custom_message_to_dict.py +85 -74
  5. clarifai_grpc/channel/errors.py +5 -0
  6. clarifai_grpc/channel/exceptions.py +1 -1
  7. clarifai_grpc/channel/grpc_json_channel.py +387 -261
  8. clarifai_grpc/channel/http_client.py +125 -104
  9. clarifai_grpc/grpc/api/resources_pb2.py +984 -7266
  10. clarifai_grpc/grpc/api/resources_pb2.pyi +17925 -0
  11. clarifai_grpc/grpc/api/resources_pb2_grpc.py +1 -0
  12. clarifai_grpc/grpc/api/service_pb2.py +1578 -8672
  13. clarifai_grpc/grpc/api/service_pb2.pyi +18269 -0
  14. clarifai_grpc/grpc/api/service_pb2_grpc.py +9746 -1859
  15. clarifai_grpc/grpc/api/status/status_code_pb2.py +33 -1334
  16. clarifai_grpc/grpc/api/status/status_code_pb2.pyi +1210 -0
  17. clarifai_grpc/grpc/api/status/status_code_pb2_grpc.py +1 -0
  18. clarifai_grpc/grpc/api/status/status_pb2.py +23 -149
  19. clarifai_grpc/grpc/api/status/status_pb2.pyi +174 -0
  20. clarifai_grpc/grpc/api/status/status_pb2_grpc.py +1 -0
  21. clarifai_grpc/grpc/api/utils/extensions_pb2.py +13 -46
  22. clarifai_grpc/grpc/api/utils/extensions_pb2.pyi +39 -0
  23. clarifai_grpc/grpc/api/utils/extensions_pb2_grpc.py +1 -0
  24. clarifai_grpc/grpc/api/utils/matrix_pb2.py +26 -0
  25. clarifai_grpc/grpc/api/utils/matrix_pb2.pyi +53 -0
  26. clarifai_grpc/grpc/api/utils/matrix_pb2_grpc.py +4 -0
  27. clarifai_grpc/grpc/api/utils/test_proto_pb2.py +17 -158
  28. clarifai_grpc/grpc/api/utils/test_proto_pb2.pyi +107 -0
  29. clarifai_grpc/grpc/api/utils/test_proto_pb2_grpc.py +1 -0
  30. clarifai_grpc/grpc/auth/scope/scope_pb2.py +245 -448
  31. clarifai_grpc/grpc/auth/scope/scope_pb2.pyi +550 -0
  32. clarifai_grpc/grpc/auth/scope/scope_pb2_grpc.py +1 -0
  33. clarifai_grpc/grpc/auth/types/types_pb2.py +11 -62
  34. clarifai_grpc/grpc/auth/types/types_pb2.pyi +78 -0
  35. clarifai_grpc/grpc/auth/types/types_pb2_grpc.py +1 -0
  36. clarifai_grpc/grpc/auth/util/extension_pb2.py +14 -68
  37. clarifai_grpc/grpc/auth/util/extension_pb2.pyi +68 -0
  38. clarifai_grpc/grpc/auth/util/extension_pb2_grpc.py +1 -0
  39. clarifai_grpc-11.10.3.dist-info/METADATA +124 -0
  40. clarifai_grpc-11.10.3.dist-info/RECORD +53 -0
  41. {clarifai_grpc-6.4.0.dist-info → clarifai_grpc-11.10.3.dist-info}/WHEEL +1 -1
  42. {clarifai_grpc-6.4.0.dist-info → clarifai_grpc-11.10.3.dist-info}/top_level.txt +0 -2
  43. clarifai_grpc-6.4.0.dist-info/METADATA +0 -88
  44. clarifai_grpc-6.4.0.dist-info/RECORD +0 -46
  45. scripts/__init__.py +0 -0
  46. scripts/app_and_key_for_tests.py +0 -180
  47. tests/__init__.py +0 -0
  48. tests/helpers.py +0 -105
  49. tests/test_integration.py +0 -243
  50. {clarifai_grpc-6.4.0.dist-info → clarifai_grpc-11.10.3.dist-info/licenses}/LICENSE +0 -0
clarifai_grpc/__init__.py CHANGED
@@ -0,0 +1,33 @@
1
+ __version__ = "11.10.3"
2
+
3
+ import os
4
+
5
+ # pop off env var set to the old python implementation
6
+ unset = False
7
+ if os.environ.get('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', None) == 'python':
8
+ unset = True
9
+ os.environ.pop('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION')
10
+ # Don't use the clarifai logger since it is in the SDK package and depends on protobuf.
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ if unset:
16
+ logger.warning(
17
+ "Unsetting PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python env var while importing clarifai package. It's best to unset this env var in your environemnt for faster performance."
18
+ )
19
+
20
+ try:
21
+ from google.protobuf.internal import api_implementation
22
+
23
+ if api_implementation.Type() == 'python':
24
+ logger.warning(
25
+ "The python version of google protobuf is being used. We recommend that you upgrade the protobuf package >=4.21.0 to use the upd version which is much faster."
26
+ )
27
+ # and not os.environ.get('CLARIFAI_SKIP_PROTOBUF_CHECK', 'false') == 'true'
28
+ # ):
29
+ # raise Exception(
30
+ # "We do not recommend running this library with the Python implementation of Protocol Buffers. Please check your installation to use the cpp or upb implementation. We recommend setting the environment variable PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=upb. You can skip this check by setting CLARIFAI_SKIP_PROTOBUF_CHECK=true"
31
+ # )
32
+ except ImportError:
33
+ pass
@@ -1,61 +1,167 @@
1
+ import json
1
2
  import os
2
3
 
3
- import requests
4
+ import grpc
4
5
 
5
6
  from clarifai_grpc.channel.grpc_json_channel import GRPCJSONChannel
6
- from clarifai_grpc.grpc.api import service_pb2_grpc
7
-
8
7
 
9
8
  RETRIES = 2 # if connections fail retry a couple times.
10
- CONNECTIONS = 20 # number of connections to maintain in pool.
9
+ CONNECTIONS = (
10
+ 20 # number of connections to maintain in pool, only usd for json channel, not direct GRPC.
11
+ )
12
+ MAX_MESSAGE_LENGTH = 1024 * 1024 * 1024 # 1GB
11
13
 
12
14
  wrap_response_deserializer = None
13
15
 
16
+ grpc_json_config = json.dumps(
17
+ {
18
+ "methodConfig": [
19
+ {
20
+ "name": [{"service": "clarifai.api.V2"}],
21
+ "retryPolicy": {
22
+ "maxAttempts": 5,
23
+ "initialBackoff": "0.01s",
24
+ "maxBackoff": "5s",
25
+ "backoffMultiplier": 2,
26
+ "retryableStatusCodes": ["UNAVAILABLE"],
27
+ },
28
+ }
29
+ ]
30
+ }
31
+ )
32
+
14
33
 
15
34
  def _response_deserializer_for_json(response_deserializer):
16
- return response_deserializer
35
+ return response_deserializer
17
36
 
18
37
 
19
38
  def _response_deserializer_for_grpc(response_deserializer):
20
- return response_deserializer.FromString
39
+ return response_deserializer.FromString
21
40
 
22
41
 
23
42
  class ClarifaiChannel:
24
- @classmethod
25
- def get_json_channel(
26
- cls,
27
- base_url=os.environ.get('CLARIFAI_API_BASE', 'https://api.clarifai.com')
28
- ):
29
- global wrap_response_deserializer
30
- wrap_response_deserializer = _response_deserializer_for_json
31
-
32
- session = cls._make_requests_session()
33
-
34
- return GRPCJSONChannel(session=session, base_url=base_url)
35
-
36
- @staticmethod
37
- def _make_requests_session():
38
- http_adapter = requests.adapters.HTTPAdapter(
39
- max_retries=RETRIES, pool_connections=CONNECTIONS, pool_maxsize=CONNECTIONS)
40
-
41
- session = requests.Session()
42
- session.mount('http://', http_adapter)
43
- session.mount('https://', http_adapter)
44
- return session
45
-
46
- @staticmethod
47
- def get_grpc_channel():
48
- global wrap_response_deserializer
49
- wrap_response_deserializer = _response_deserializer_for_grpc
50
-
51
- return service_pb2_grpc.grpc.secure_channel(
52
- 'api-grpc.clarifai.com:18081',
53
- service_pb2_grpc.grpc.ssl_channel_credentials()
54
- )
55
-
56
- @staticmethod
57
- def get_insecure_grpc_channel():
58
- global wrap_response_deserializer
59
- wrap_response_deserializer = _response_deserializer_for_grpc
60
-
61
- return service_pb2_grpc.grpc.insecure_channel('api-grpc.clarifai.com:18080')
43
+ @classmethod
44
+ def get_json_channel(
45
+ cls, base_url=os.environ.get("CLARIFAI_API_BASE", "https://api.clarifai.com")
46
+ ):
47
+ global wrap_response_deserializer
48
+ wrap_response_deserializer = _response_deserializer_for_json
49
+
50
+ session = cls._make_requests_session()
51
+
52
+ return GRPCJSONChannel(session=session, base_url=base_url)
53
+
54
+ @staticmethod
55
+ def _make_requests_session():
56
+ import requests # noqa
57
+
58
+ http_adapter = requests.adapters.HTTPAdapter(
59
+ max_retries=RETRIES, pool_connections=CONNECTIONS, pool_maxsize=CONNECTIONS
60
+ )
61
+
62
+ session = requests.Session()
63
+ session.mount("http://", http_adapter)
64
+ session.mount("https://", http_adapter)
65
+ return session
66
+
67
+ @staticmethod
68
+ def get_grpc_channel(base=None, root_certificates_path=None):
69
+ global wrap_response_deserializer
70
+ wrap_response_deserializer = _response_deserializer_for_grpc
71
+
72
+ if not base:
73
+ base = os.environ.get("CLARIFAI_GRPC_BASE", "api.clarifai.com")
74
+ if base.startswith("http:") or base.startswith("https:"):
75
+ raise ValueError(
76
+ "For secure channels the 'base' passed via arguments or env variable CLARIFAI_GRPC_BASE should not start with http:// or https:// but be a direct api endpoint like 'api.clarifai.com'"
77
+ )
78
+
79
+ if root_certificates_path:
80
+ with open(root_certificates_path, "rb") as f:
81
+ root_certificates = f.read()
82
+ credentials = grpc.ssl_channel_credentials(root_certificates)
83
+ else:
84
+ credentials = grpc.ssl_channel_credentials()
85
+
86
+ return grpc.secure_channel(
87
+ base,
88
+ credentials,
89
+ options=[
90
+ ("grpc.service_config", grpc_json_config),
91
+ ("grpc.max_receive_message_length", MAX_MESSAGE_LENGTH),
92
+ ("grpc.max_send_message_length", MAX_MESSAGE_LENGTH),
93
+ ],
94
+ )
95
+
96
+ @staticmethod
97
+ def get_insecure_grpc_channel(base=None, port=18080):
98
+ global wrap_response_deserializer
99
+ wrap_response_deserializer = _response_deserializer_for_grpc
100
+
101
+ if not base:
102
+ base = os.environ.get("CLARIFAI_GRPC_BASE", None)
103
+
104
+ if not base:
105
+ raise ValueError("Please set 'base' via arguments or env variable CLARIFAI_GRPC_BASE")
106
+
107
+ channel_address = "{}:{}".format(base, port)
108
+
109
+ return grpc.insecure_channel(
110
+ channel_address,
111
+ options=[
112
+ ("grpc.service_config", grpc_json_config),
113
+ ("grpc.max_receive_message_length", MAX_MESSAGE_LENGTH),
114
+ ("grpc.max_send_message_length", MAX_MESSAGE_LENGTH),
115
+ ],
116
+ )
117
+
118
+ @staticmethod
119
+ def get_aio_grpc_channel(base=None, root_certificates_path=None):
120
+ global wrap_response_deserializer
121
+ wrap_response_deserializer = _response_deserializer_for_grpc
122
+
123
+ if not base:
124
+ base = os.environ.get("CLARIFAI_GRPC_BASE", "api.clarifai.com")
125
+ if base.startswith("http:") or base.startswith("https:"):
126
+ raise ValueError(
127
+ "For secure channels the 'base' passed via arguments or env variable CLARIFAI_GRPC_BASE should not start with http:// or https:// but be a direct api endpoint like 'api.clarifai.com'"
128
+ )
129
+
130
+ if root_certificates_path:
131
+ with open(root_certificates_path, "rb") as f:
132
+ root_certificates = f.read()
133
+ credentials = grpc.ssl_channel_credentials(root_certificates)
134
+ else:
135
+ credentials = grpc.ssl_channel_credentials()
136
+
137
+ return grpc.aio.secure_channel(
138
+ base,
139
+ credentials,
140
+ options=[
141
+ ("grpc.service_config", grpc_json_config),
142
+ ("grpc.max_receive_message_length", MAX_MESSAGE_LENGTH),
143
+ ("grpc.max_send_message_length", MAX_MESSAGE_LENGTH),
144
+ ],
145
+ )
146
+
147
+ @staticmethod
148
+ def get_aio_insecure_grpc_channel(base=None, port=18080):
149
+ global wrap_response_deserializer
150
+ wrap_response_deserializer = _response_deserializer_for_grpc
151
+
152
+ if not base:
153
+ base = os.environ.get("CLARIFAI_GRPC_BASE", None)
154
+
155
+ if not base:
156
+ raise ValueError("Please set 'base' via arguments or env variable CLARIFAI_GRPC_BASE")
157
+
158
+ channel_address = "{}:{}".format(base, port)
159
+
160
+ return grpc.aio.insecure_channel(
161
+ channel_address,
162
+ options=[
163
+ ("grpc.service_config", grpc_json_config),
164
+ ("grpc.max_receive_message_length", MAX_MESSAGE_LENGTH),
165
+ ("grpc.max_send_message_length", MAX_MESSAGE_LENGTH),
166
+ ],
167
+ )
@@ -3,43 +3,50 @@ from google.protobuf.message import Message # noqa
3
3
 
4
4
  from clarifai_grpc.grpc.api.utils import extensions_pb2
5
5
 
6
- # Python 3 deprecates getargspec and introduces getfullargspec, which Python 2 doesn't have.
7
- try:
8
- from inspect import getfullargspec as get_args
9
- except ImportError:
10
- from inspect import getargspec as get_args
11
6
 
7
+ def dict_to_protobuf(
8
+ protobuf_class,
9
+ js_dict,
10
+ ignore_unknown_fields=False,
11
+ descriptor_pool=None,
12
+ max_recursion_depth=100,
13
+ ):
14
+ """Parses a JSON dictionary representation into a message.
15
+
16
+ Args:
17
+ js_dict: Dict representation of a JSON message.
18
+ message: A protocol buffer message to merge into.
19
+ ignore_unknown_fields: If True, do not raise errors for unknown fields.
20
+ descriptor_pool: A Descriptor Pool for resolving types. If None use the
21
+ default.
22
+ max_recursion_depth: max recursion depth of JSON message to be
23
+ deserialized. JSON messages over this depth will fail to be
24
+ deserialized. Default value is 100.
25
+
26
+ Returns:
27
+ The same message passed as argument.
28
+ """
29
+ message = protobuf_class()
12
30
 
13
- def dict_to_protobuf(protobuf_class, js_dict, ignore_unknown_fields=False):
14
- # type: (type(Message), dict, bool) -> Message
15
- message = protobuf_class()
16
-
17
- # Protobuf versions 3.6.* and 3.7.0 require a different number of parameters in the _Parser's
18
- # constructor. In the case of 3.6.*, we pass only the argument ignore_unknown_fields, but in
19
- # the case of 3.7.0, we pass in one additional None parameter. To be future proof(ish), pass in
20
- # None to any subsequent parameter.
21
- num_of_args = len(get_args(_Parser.__init__).args)
22
- none_args = [None] * (num_of_args - 2) # Subtract 2 for self and ignore_unknown_fields.
23
- parser = _CustomParser(ignore_unknown_fields, *none_args)
31
+ parser = _CustomParser(ignore_unknown_fields, descriptor_pool, max_recursion_depth)
24
32
 
25
- parser.ConvertMessage(js_dict, message)
26
- return message
33
+ parser.ConvertMessage(js_dict, message, path="")
34
+ return message
27
35
 
28
36
 
29
37
  class _CustomParser(_Parser):
30
-
31
- def _ConvertFieldValuePair(self, js, message):
32
- """
33
- Because of fields with custom extensions such as cl_default_float, we need
34
- to adjust the original's method's JSON object parameter by setting them explicitly to the
35
- default value.
36
- """
37
-
38
- message_descriptor = message.DESCRIPTOR
39
- for f in message_descriptor.fields:
40
- default_float = f.GetOptions().Extensions[extensions_pb2.cl_default_float]
41
- if default_float:
42
- if f.name not in js:
43
- js[f.name] = default_float
44
-
45
- super(_CustomParser, self)._ConvertFieldValuePair(js, message)
38
+ def _ConvertFieldValuePair(self, js, message, path):
39
+ """
40
+ Because of fields with custom extensions such as cl_default_float, we need
41
+ to adjust the original's method's JSON object parameter by setting them explicitly to the
42
+ default value.
43
+ """
44
+
45
+ message_descriptor = message.DESCRIPTOR
46
+ for f in message_descriptor.fields:
47
+ default_float = f.GetOptions().Extensions[extensions_pb2.cl_default_float]
48
+ if default_float:
49
+ if f.name not in js:
50
+ js[f.name] = default_float
51
+
52
+ super(_CustomParser, self)._ConvertFieldValuePair(js, message, path)
@@ -8,81 +8,92 @@ from clarifai_grpc.grpc.api.utils import extensions_pb2
8
8
 
9
9
 
10
10
  def protobuf_to_dict(object_protobuf, use_integers_for_enums=True, ignore_show_empty=False):
11
- # type: (Message, typing.Optional[bool], typing.Optional[bool]) -> dict
11
+ # type: (Message, typing.Optional[bool], typing.Optional[bool]) -> dict
12
12
 
13
- # printer = _CustomPrinter(
14
- printer = _CustomPrinter(
15
- including_default_value_fields=False,
16
- preserving_proto_field_name=True,
17
- use_integers_for_enums=use_integers_for_enums,
18
- ignore_show_empty=ignore_show_empty)
19
- # pylint: disable=protected-access
20
- return printer._MessageToJsonObject(object_protobuf)
13
+ # printer = _CustomPrinter(
14
+ printer = _CustomPrinter(
15
+ including_default_value_fields=False,
16
+ preserving_proto_field_name=True,
17
+ use_integers_for_enums=use_integers_for_enums,
18
+ ignore_show_empty=ignore_show_empty,
19
+ )
20
+ # pylint: disable=protected-access
21
+ return printer._MessageToJsonObject(object_protobuf)
21
22
 
22
23
 
23
24
  class _CustomPrinter(_Printer):
24
-
25
- def __init__(self, including_default_value_fields, preserving_proto_field_name,
26
- use_integers_for_enums, ignore_show_empty):
27
- super(_CustomPrinter, self).__init__(including_default_value_fields,
28
- preserving_proto_field_name, use_integers_for_enums)
29
- self._ignore_show_empty = ignore_show_empty
30
-
31
- def _RegularMessageToJsonObject(self, message, js):
32
- """
33
- Because of the fields with the custom extension `cl_show_if_empty`, we need to adjust the
34
- original's method's return JSON object and keep these fields.
35
- """
36
-
37
- js = super(_CustomPrinter, self)._RegularMessageToJsonObject(message, js)
38
-
39
- message_descriptor = message.DESCRIPTOR
40
- for field in message_descriptor.fields:
41
-
42
- if (self._ignore_show_empty and
43
- not field.GetOptions().Extensions[extensions_pb2.cl_default_float]):
44
- continue
45
- if not field.GetOptions().Extensions[extensions_pb2.cl_show_if_empty]:
46
- continue
47
-
48
- # Singular message fields and oneof fields will not be affected.
49
- if ((field.label != descriptor.FieldDescriptor.LABEL_REPEATED and
50
- field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE) or
51
- field.containing_oneof):
52
- continue
53
- if self.preserving_proto_field_name:
54
- name = field.name
55
- else:
56
- name = field.json_name
57
- if name in js:
58
- # Skip the field which has been serialized already.
59
- continue
60
- if _IsMapEntry(field):
61
- js[name] = {}
62
- elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
63
- js[name] = []
64
- else:
65
- js[name] = self._FieldToJsonObject(field, field.default_value)
66
-
67
- return js
68
-
69
- def _StructMessageToJsonObject(self, message):
70
- """
71
- Converts Struct message according to Proto3 JSON Specification.
72
-
73
- However, by default, empty objects {} get converted to null. We overwrite this behavior so {}
74
- get converted to {}.
75
- """
76
-
77
- fields = message.fields
78
- ret = {}
79
- for key in fields:
80
- # When there's a Struct with an empty Struct field, this condition will hold True.
81
- # Far as I know this is the only case this condition will be true. If not, this condition
82
- # needs to be amended.
83
- if fields[key].WhichOneof('kind') is None:
84
- json_object = {}
85
- else:
86
- json_object = self._ValueMessageToJsonObject(fields[key])
87
- ret[key] = json_object
88
- return ret
25
+ def __init__(
26
+ self,
27
+ *,
28
+ including_default_value_fields,
29
+ preserving_proto_field_name,
30
+ use_integers_for_enums,
31
+ ignore_show_empty,
32
+ ):
33
+ super(_CustomPrinter, self).__init__(
34
+ always_print_fields_with_no_presence=including_default_value_fields,
35
+ preserving_proto_field_name=preserving_proto_field_name,
36
+ use_integers_for_enums=use_integers_for_enums,
37
+ )
38
+ self._ignore_show_empty = ignore_show_empty
39
+
40
+ def _RegularMessageToJsonObject(self, message, js):
41
+ """
42
+ Because of the fields with the custom extension `cl_show_if_empty`, we need to adjust the
43
+ original's method's return JSON object and keep these fields.
44
+ """
45
+
46
+ js = super(_CustomPrinter, self)._RegularMessageToJsonObject(message, js)
47
+
48
+ message_descriptor = message.DESCRIPTOR
49
+ for field in message_descriptor.fields:
50
+ if (
51
+ self._ignore_show_empty
52
+ and not field.GetOptions().Extensions[extensions_pb2.cl_default_float]
53
+ ):
54
+ continue
55
+ if not field.GetOptions().Extensions[extensions_pb2.cl_show_if_empty]:
56
+ continue
57
+
58
+ # Singular message fields and oneof fields will not be affected.
59
+ if (
60
+ field.label != descriptor.FieldDescriptor.LABEL_REPEATED
61
+ and field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE
62
+ ) or field.containing_oneof:
63
+ continue
64
+ if self.preserving_proto_field_name:
65
+ name = field.name
66
+ else:
67
+ name = field.json_name
68
+ if name in js:
69
+ # Skip the field which has been serialized already.
70
+ continue
71
+ if _IsMapEntry(field):
72
+ js[name] = {}
73
+ elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
74
+ js[name] = []
75
+ else:
76
+ js[name] = self._FieldToJsonObject(field, field.default_value)
77
+
78
+ return js
79
+
80
+ def _StructMessageToJsonObject(self, message):
81
+ """
82
+ Converts Struct message according to Proto3 JSON Specification.
83
+
84
+ However, by default, empty objects {} get converted to null. We overwrite this behavior so {}
85
+ get converted to {}.
86
+ """
87
+
88
+ fields = message.fields
89
+ ret = {}
90
+ for key in fields:
91
+ # When there's a Struct with an empty Struct field, this condition will hold True.
92
+ # Far as I know this is the only case this condition will be true. If not, this condition
93
+ # needs to be amended.
94
+ if fields[key].WhichOneof("kind") is None:
95
+ json_object = {}
96
+ else:
97
+ json_object = self._ValueMessageToJsonObject(fields[key])
98
+ ret[key] = json_object
99
+ return ret
@@ -2,5 +2,10 @@ class ApiError(Exception):
2
2
  pass
3
3
 
4
4
 
5
+ class NotImplementedCaller:
6
+ def __call__(self, *args, **kwargs):
7
+ raise NotImplementedError()
8
+
9
+
5
10
  class UsageError(Exception):
6
11
  pass
@@ -1,2 +1,2 @@
1
1
  class ClarifaiException(Exception):
2
- pass
2
+ pass