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.
- clarifai_grpc/__init__.py +33 -0
- clarifai_grpc/channel/clarifai_channel.py +150 -44
- clarifai_grpc/channel/custom_converters/custom_dict_to_message.py +41 -34
- clarifai_grpc/channel/custom_converters/custom_message_to_dict.py +85 -74
- clarifai_grpc/channel/errors.py +5 -0
- clarifai_grpc/channel/exceptions.py +1 -1
- clarifai_grpc/channel/grpc_json_channel.py +387 -261
- clarifai_grpc/channel/http_client.py +125 -104
- clarifai_grpc/grpc/api/resources_pb2.py +984 -7266
- clarifai_grpc/grpc/api/resources_pb2.pyi +17925 -0
- clarifai_grpc/grpc/api/resources_pb2_grpc.py +1 -0
- clarifai_grpc/grpc/api/service_pb2.py +1578 -8672
- clarifai_grpc/grpc/api/service_pb2.pyi +18269 -0
- clarifai_grpc/grpc/api/service_pb2_grpc.py +9746 -1859
- clarifai_grpc/grpc/api/status/status_code_pb2.py +33 -1334
- clarifai_grpc/grpc/api/status/status_code_pb2.pyi +1210 -0
- clarifai_grpc/grpc/api/status/status_code_pb2_grpc.py +1 -0
- clarifai_grpc/grpc/api/status/status_pb2.py +23 -149
- clarifai_grpc/grpc/api/status/status_pb2.pyi +174 -0
- clarifai_grpc/grpc/api/status/status_pb2_grpc.py +1 -0
- clarifai_grpc/grpc/api/utils/extensions_pb2.py +13 -46
- clarifai_grpc/grpc/api/utils/extensions_pb2.pyi +39 -0
- clarifai_grpc/grpc/api/utils/extensions_pb2_grpc.py +1 -0
- clarifai_grpc/grpc/api/utils/matrix_pb2.py +26 -0
- clarifai_grpc/grpc/api/utils/matrix_pb2.pyi +53 -0
- clarifai_grpc/grpc/api/utils/matrix_pb2_grpc.py +4 -0
- clarifai_grpc/grpc/api/utils/test_proto_pb2.py +17 -158
- clarifai_grpc/grpc/api/utils/test_proto_pb2.pyi +107 -0
- clarifai_grpc/grpc/api/utils/test_proto_pb2_grpc.py +1 -0
- clarifai_grpc/grpc/auth/scope/scope_pb2.py +245 -448
- clarifai_grpc/grpc/auth/scope/scope_pb2.pyi +550 -0
- clarifai_grpc/grpc/auth/scope/scope_pb2_grpc.py +1 -0
- clarifai_grpc/grpc/auth/types/types_pb2.py +11 -62
- clarifai_grpc/grpc/auth/types/types_pb2.pyi +78 -0
- clarifai_grpc/grpc/auth/types/types_pb2_grpc.py +1 -0
- clarifai_grpc/grpc/auth/util/extension_pb2.py +14 -68
- clarifai_grpc/grpc/auth/util/extension_pb2.pyi +68 -0
- clarifai_grpc/grpc/auth/util/extension_pb2_grpc.py +1 -0
- clarifai_grpc-11.10.3.dist-info/METADATA +124 -0
- clarifai_grpc-11.10.3.dist-info/RECORD +53 -0
- {clarifai_grpc-6.4.0.dist-info → clarifai_grpc-11.10.3.dist-info}/WHEEL +1 -1
- {clarifai_grpc-6.4.0.dist-info → clarifai_grpc-11.10.3.dist-info}/top_level.txt +0 -2
- clarifai_grpc-6.4.0.dist-info/METADATA +0 -88
- clarifai_grpc-6.4.0.dist-info/RECORD +0 -46
- scripts/__init__.py +0 -0
- scripts/app_and_key_for_tests.py +0 -180
- tests/__init__.py +0 -0
- tests/helpers.py +0 -105
- tests/test_integration.py +0 -243
- {clarifai_grpc-6.4.0.dist-info → clarifai_grpc-11.10.3.dist-info/licenses}/LICENSE +0 -0
|
@@ -3,294 +3,420 @@ import logging
|
|
|
3
3
|
import re
|
|
4
4
|
import typing # noqa
|
|
5
5
|
|
|
6
|
-
import requests # noqa
|
|
7
6
|
from google.protobuf.descriptor import Descriptor # noqa
|
|
8
7
|
from google.protobuf.message import Message # noqa
|
|
9
8
|
|
|
10
9
|
from clarifai_grpc.channel import http_client
|
|
11
10
|
from clarifai_grpc.channel.custom_converters.custom_dict_to_message import dict_to_protobuf
|
|
12
11
|
from clarifai_grpc.channel.custom_converters.custom_message_to_dict import protobuf_to_dict
|
|
13
|
-
from clarifai_grpc.channel.errors import UsageError
|
|
12
|
+
from clarifai_grpc.channel.errors import NotImplementedCaller, UsageError
|
|
13
|
+
from clarifai_grpc.channel.exceptions import ClarifaiException
|
|
14
14
|
from clarifai_grpc.grpc.api.service_pb2 import _V2
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
BASE_URL = "https://api.clarifai.com"
|
|
18
|
-
URL_TEMPLATE_PARAM_REGEX = re.compile(r
|
|
17
|
+
URL_TEMPLATE_PARAM_REGEX = re.compile(r"\{{1}(.*?)\}{1}")
|
|
19
18
|
|
|
20
|
-
logger = logging.getLogger(
|
|
19
|
+
logger = logging.getLogger("clarifai")
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
class GRPCJSONChannel(object):
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Currently there is only support for unary_unary requests. If you have any other type of grpc
|
|
28
|
-
request this channel will nicely fail when trying to use within a grpc stub.
|
|
29
|
-
|
|
30
|
-
Example:
|
|
31
|
-
Assuming your top level endpoints are called V2 and in a proto/clarifai/api/endpoint.proto file,
|
|
32
|
-
then you build those in python and can import the spec to use in GRPCJSONChannel as follows:
|
|
33
|
-
|
|
34
|
-
from clarifai.rest.grpc.proto.clarifai.api.endpoint_pb2_grpc import V2Stub
|
|
35
|
-
from clarifai.rest.grpc.proto.clarifai.api.endpoint_pb2 import _V2
|
|
36
|
-
channel = GRPCJSONChannel(key="api key", service_descriptor=_V2)
|
|
37
|
-
stub = V2Stub(channel)
|
|
38
|
-
|
|
39
|
-
# Then you can use the stub to call just like grpc directly!!!
|
|
40
|
-
result = stub.PostInputs(PostInputsRequest(inputs=[Input(data=Data(image=Image(
|
|
41
|
-
url="http://...")))]))
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
def __init__(
|
|
45
|
-
self,
|
|
46
|
-
session: requests.Session,
|
|
47
|
-
base_url: str = BASE_URL,
|
|
48
|
-
service_descriptor: typing.Any = _V2
|
|
49
|
-
) -> None:
|
|
50
|
-
"""
|
|
51
|
-
Args:
|
|
52
|
-
session: a request session
|
|
53
|
-
base_url: if you want to point at a different url than the default.
|
|
54
|
-
service_descriptor: This is a ServiceDescriptor object found in the compiled grpc-gateway
|
|
55
|
-
.proto results. For example if your proto defining the endpoints is in endpoint.proto then look
|
|
56
|
-
in endpoint_pb2.py file for ServiceDescriptor and use that.
|
|
57
|
-
"""
|
|
58
|
-
self.session = session
|
|
59
|
-
self.name_to_resources = {}
|
|
60
|
-
|
|
61
|
-
for m in service_descriptor.methods:
|
|
62
|
-
# This gets the google.api.http object from the .proto file that looks like this:
|
|
63
|
-
# option (google.api.http) = {
|
|
64
|
-
# delete: "/v2/users/{user_app_id.user_id}/apps/{user_app_id.app_id}/models/{model_id}"
|
|
65
|
-
# additional_bindings {
|
|
66
|
-
# delete: "/v2/models/{model_id}"
|
|
67
|
-
# }
|
|
68
|
-
# Then we check if there are additional_bindings and use that if so (because we've had the
|
|
69
|
-
# convention of having the default urls in there and the not yet used urls at the top level.
|
|
70
|
-
|
|
71
|
-
for field in m.GetOptions().ListFields():
|
|
72
|
-
if field[0].name == "http":
|
|
73
|
-
base_http_rule = field[1]
|
|
74
|
-
break
|
|
75
|
-
else:
|
|
76
|
-
raise Exception("Method %s has no 'http' field" % m.full_name)
|
|
77
|
-
|
|
78
|
-
protobuf_name = '/' + service_descriptor.full_name + '/' + m.name
|
|
79
|
-
self.name_to_resources[protobuf_name] = (m.input_type, [])
|
|
80
|
-
|
|
81
|
-
for http_rule in base_http_rule.additional_bindings or [base_http_rule]:
|
|
82
|
-
# Get the url template and the method to use for http.
|
|
83
|
-
if http_rule.HasField('get'):
|
|
84
|
-
method = 'GET'
|
|
85
|
-
url_template = base_url + http_rule.get
|
|
86
|
-
elif http_rule.HasField('post'):
|
|
87
|
-
method = 'POST'
|
|
88
|
-
url_template = base_url + http_rule.post
|
|
89
|
-
elif http_rule.HasField('patch'):
|
|
90
|
-
method = 'PATCH'
|
|
91
|
-
url_template = base_url + http_rule.patch
|
|
92
|
-
elif http_rule.HasField('put'):
|
|
93
|
-
method = 'PUT'
|
|
94
|
-
url_template = base_url + http_rule.put
|
|
95
|
-
elif http_rule.HasField('delete'):
|
|
96
|
-
method = 'DELETE'
|
|
97
|
-
url_template = base_url + http_rule.delete
|
|
98
|
-
else:
|
|
99
|
-
raise Exception("Failed to parse the grpc-gateway service spec.")
|
|
100
|
-
|
|
101
|
-
self.name_to_resources[protobuf_name][1].append((url_template, method))
|
|
102
|
-
|
|
103
|
-
def unary_unary(self, name, request_serializer, response_deserializer):
|
|
104
|
-
# type: (str, typing.Callable, typing.Callable) -> JSONUnaryUnary
|
|
105
|
-
""" Method to create the callable JSONUnaryUnary. """
|
|
106
|
-
request_message_descriptor, resources = self.name_to_resources[name]
|
|
107
|
-
return JSONUnaryUnary(self.session, request_message_descriptor, resources, request_serializer,
|
|
108
|
-
response_deserializer)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class JSONUnaryUnary(object):
|
|
112
|
-
""" This mimics the unary_unary calls and is actually the thing doing the http requests.
|
|
113
|
-
"""
|
|
114
|
-
|
|
115
|
-
def __init__(
|
|
116
|
-
self,
|
|
117
|
-
session, # type: requests.Session
|
|
118
|
-
request_message_descriptor, # type: Descriptor
|
|
119
|
-
resources, # type: typing.List[typing.Tuple[str, typing.Any]]
|
|
120
|
-
request_serializer, # type: typing.Callable
|
|
121
|
-
response_deserializer # type: typing.Callable
|
|
122
|
-
):
|
|
123
|
-
# type: (...) -> None
|
|
124
|
-
"""
|
|
125
|
-
Args:
|
|
126
|
-
session: a request session
|
|
127
|
-
request_message_descriptor: this is a MessageDescriptor for the input type.
|
|
128
|
-
resources: a list of available resource endpoints
|
|
129
|
-
request_serializer: the method to use to serialize the request proto
|
|
130
|
-
response_deserializer: the response proto deserializer which will be used to convert the http
|
|
131
|
-
response will be parsed into this.
|
|
23
|
+
"""This mimics the behaviour of a grpc channel object but allows transport over https with
|
|
24
|
+
json request and response bodies.
|
|
132
25
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
"""
|
|
136
|
-
self.session = session
|
|
137
|
-
self.request_message_descriptor = request_message_descriptor
|
|
138
|
-
self.resources = resources
|
|
139
|
-
self.request_serializer = request_serializer
|
|
140
|
-
self.response_deserializer = response_deserializer
|
|
26
|
+
Currently there is only support for unary_unary requests. If you have any other type of grpc
|
|
27
|
+
request this channel will nicely fail when trying to use within a grpc stub.
|
|
141
28
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
29
|
+
Example:
|
|
30
|
+
Assuming your top level endpoints are called V2 and in a proto/clarifai/api/endpoint.proto file,
|
|
31
|
+
then you build those in python and can import the spec to use in GRPCJSONChannel as follows:
|
|
145
32
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
metadata: the authorization string (either API key or Personal Access Token)
|
|
33
|
+
from clarifai.rest.grpc.proto.clarifai.api.endpoint_pb2_grpc import V2Stub
|
|
34
|
+
from clarifai.rest.grpc.proto.clarifai.api.endpoint_pb2 import _V2
|
|
35
|
+
channel = GRPCJSONChannel(key="api key", service_descriptor=_V2)
|
|
36
|
+
stub = V2Stub(channel)
|
|
151
37
|
|
|
152
|
-
|
|
153
|
-
|
|
38
|
+
# Then you can use the stub to call just like grpc directly!!!
|
|
39
|
+
result = stub.PostInputs(PostInputsRequest(inputs=[Input(data=Data(image=Image(
|
|
40
|
+
url="http://...")))]))
|
|
154
41
|
"""
|
|
155
|
-
# if metadata is not None:
|
|
156
|
-
# raise Exception("No support currently for metadata field.")
|
|
157
|
-
|
|
158
|
-
# There is no __self__ attribute on the request_serializer unfortunately.
|
|
159
|
-
expected_object_name = self.request_message_descriptor.name
|
|
160
|
-
if type(request).__name__ != expected_object_name:
|
|
161
|
-
raise Exception("The input request must be of type: %s from %s" %
|
|
162
|
-
(expected_object_name, self.request_message_descriptor.file.name))
|
|
163
|
-
|
|
164
|
-
params = protobuf_to_dict(request, use_integers_for_enums=False, ignore_show_empty=True)
|
|
165
|
-
|
|
166
|
-
url, method, url_fields = _pick_proper_endpoint(self.resources, params)
|
|
167
42
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
session, # type: requests.Session,
|
|
46
|
+
base_url: str = BASE_URL,
|
|
47
|
+
service_descriptor: typing.Any = _V2,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Args:
|
|
51
|
+
session: a request session
|
|
52
|
+
base_url: if you want to point at a different url than the default.
|
|
53
|
+
service_descriptor: This is a ServiceDescriptor object found in the compiled grpc-gateway
|
|
54
|
+
.proto results. For example if your proto defining the endpoints is in endpoint.proto then look
|
|
55
|
+
in endpoint_pb2.py file for ServiceDescriptor and use that.
|
|
56
|
+
"""
|
|
57
|
+
self.session = session
|
|
58
|
+
self.name_to_resources = {}
|
|
59
|
+
|
|
60
|
+
for m in service_descriptor.methods:
|
|
61
|
+
# This gets the google.api.http object from the .proto file that looks like this:
|
|
62
|
+
# option (google.api.http) = {
|
|
63
|
+
# delete: "/v2/users/{user_app_id.user_id}/apps/{user_app_id.app_id}/models/{model_id}"
|
|
64
|
+
# additional_bindings {
|
|
65
|
+
# delete: "/v2/models/{model_id}"
|
|
66
|
+
# }
|
|
67
|
+
# Then we check if there are additional_bindings and use that if so (because we've had the
|
|
68
|
+
# convention of having the default urls in there and the not yet used urls at the top level.
|
|
69
|
+
|
|
70
|
+
for field in m.GetOptions().ListFields():
|
|
71
|
+
if field[0].name == "http":
|
|
72
|
+
base_http_rule = field[1]
|
|
73
|
+
break
|
|
74
|
+
else:
|
|
75
|
+
raise Exception("Method %s has no 'http' field" % m.full_name)
|
|
76
|
+
|
|
77
|
+
protobuf_name = "/" + service_descriptor.full_name + "/" + m.name
|
|
78
|
+
self.name_to_resources[protobuf_name] = (m.input_type, [])
|
|
79
|
+
|
|
80
|
+
def register_bindings(http_rule):
|
|
81
|
+
# Get the url template and the method to use for http.
|
|
82
|
+
if http_rule.HasField("get"):
|
|
83
|
+
method = "GET"
|
|
84
|
+
url_template = base_url + http_rule.get
|
|
85
|
+
elif http_rule.HasField("post"):
|
|
86
|
+
method = "POST"
|
|
87
|
+
url_template = base_url + http_rule.post
|
|
88
|
+
elif http_rule.HasField("patch"):
|
|
89
|
+
method = "PATCH"
|
|
90
|
+
url_template = base_url + http_rule.patch
|
|
91
|
+
elif http_rule.HasField("put"):
|
|
92
|
+
method = "PUT"
|
|
93
|
+
url_template = base_url + http_rule.put
|
|
94
|
+
elif http_rule.HasField("delete"):
|
|
95
|
+
method = "DELETE"
|
|
96
|
+
url_template = base_url + http_rule.delete
|
|
97
|
+
else:
|
|
98
|
+
raise Exception("Failed to parse the grpc-gateway service spec.")
|
|
99
|
+
|
|
100
|
+
self.name_to_resources[protobuf_name][1].append((url_template, method))
|
|
101
|
+
|
|
102
|
+
register_bindings(base_http_rule)
|
|
103
|
+
for http_rule in base_http_rule.additional_bindings:
|
|
104
|
+
register_bindings(http_rule)
|
|
105
|
+
|
|
106
|
+
def unary_unary(self, name, request_serializer, response_deserializer):
|
|
107
|
+
# type: (str, typing.Callable, typing.Callable) -> JSONUnaryUnary
|
|
108
|
+
"""Method to create the callable JSONUnaryUnary."""
|
|
109
|
+
request_message_descriptor, resources = self.name_to_resources[name]
|
|
110
|
+
return JSONUnaryUnary(
|
|
111
|
+
self.session,
|
|
112
|
+
request_message_descriptor,
|
|
113
|
+
resources,
|
|
114
|
+
request_serializer,
|
|
115
|
+
response_deserializer,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def unary_stream(self, method, request_serializer=None, response_deserializer=None):
|
|
119
|
+
return NotImplementedCaller()
|
|
120
|
+
|
|
121
|
+
def stream_stream(self, name, request_serializer, response_deserializer):
|
|
122
|
+
# type: (str, typing.Callable, typing.Callable) -> JSONStreamStream
|
|
123
|
+
"""Method to create the callable JSONStreamStream."""
|
|
124
|
+
request_message_descriptor, resources = self.name_to_resources[name]
|
|
125
|
+
return JSONStreamStream(
|
|
126
|
+
self.session,
|
|
127
|
+
request_message_descriptor,
|
|
128
|
+
resources,
|
|
129
|
+
request_serializer,
|
|
130
|
+
response_deserializer,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def stream_unary(self, method, request_serializer=None, response_deserializer=None):
|
|
134
|
+
return NotImplementedCaller()
|
|
171
135
|
|
|
172
|
-
auth_string = self._read_auth_string(metadata)
|
|
173
136
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
137
|
+
class JSONUnaryUnary(object):
|
|
138
|
+
"""This mimics the unary_unary calls and is actually the thing doing the http requests."""
|
|
139
|
+
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
session, # type: requests.Session
|
|
143
|
+
request_message_descriptor, # type: Descriptor
|
|
144
|
+
resources, # type: typing.List[typing.Tuple[str, typing.Any]]
|
|
145
|
+
request_serializer, # type: typing.Callable
|
|
146
|
+
response_deserializer, # type: typing.Callable
|
|
147
|
+
):
|
|
148
|
+
# type: (...) -> None
|
|
149
|
+
"""
|
|
150
|
+
Args:
|
|
151
|
+
session: a request session
|
|
152
|
+
request_message_descriptor: this is a MessageDescriptor for the input type.
|
|
153
|
+
resources: a list of available resource endpoints
|
|
154
|
+
request_serializer: the method to use to serialize the request proto
|
|
155
|
+
response_deserializer: the response proto deserializer which will be used to convert the http
|
|
156
|
+
response will be parsed into this.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
response: a proto object of class response_deserializer filled in with the response.
|
|
160
|
+
"""
|
|
161
|
+
self.session = session
|
|
162
|
+
self.request_message_descriptor = request_message_descriptor
|
|
163
|
+
self.resources = resources
|
|
164
|
+
self.request_serializer = request_serializer
|
|
165
|
+
self.response_deserializer = response_deserializer
|
|
166
|
+
|
|
167
|
+
def __call__(self, request, metadata=None): # type: (Message, tuple) -> Message
|
|
168
|
+
"""This is where the actually calls come through when the stub is called such as
|
|
169
|
+
stub.PostInputs(). They get passed to this method which actually makes the request.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
request: the proto object for the request. It must be the proper type for the request or the
|
|
173
|
+
server will complain. Note: this doesn't type check the incoming request in the client but
|
|
174
|
+
does make sure it can serialize before sending to the server atleast.
|
|
175
|
+
metadata: the authorization string (either API key or Personal Access Token)
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
response: the proto object that this method returns.
|
|
179
|
+
"""
|
|
180
|
+
# if metadata is not None:
|
|
181
|
+
# raise Exception("No support currently for metadata field.")
|
|
182
|
+
|
|
183
|
+
# There is no __self__ attribute on the request_serializer unfortunately.
|
|
184
|
+
expected_object_name = self.request_message_descriptor.name
|
|
185
|
+
if type(request).__name__ != expected_object_name:
|
|
186
|
+
raise Exception(
|
|
187
|
+
"The input request must be of type: %s from %s"
|
|
188
|
+
% (expected_object_name, self.request_message_descriptor.file.name)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
params = protobuf_to_dict(request, use_integers_for_enums=False, ignore_show_empty=True)
|
|
192
|
+
|
|
193
|
+
url, method, url_fields = _pick_proper_endpoint(self.resources, params)
|
|
194
|
+
|
|
195
|
+
for url_field in url_fields:
|
|
196
|
+
if url_field in params:
|
|
197
|
+
del params[url_field]
|
|
198
|
+
|
|
199
|
+
auth_string = self._read_auth_string(metadata)
|
|
200
|
+
|
|
201
|
+
http = http_client.HttpClient(self.session, auth_string)
|
|
202
|
+
response_json = http.execute_request(method, params, url)
|
|
203
|
+
|
|
204
|
+
# Get the actual message object to construct
|
|
205
|
+
message = self.response_deserializer
|
|
206
|
+
result = dict_to_protobuf(message, response_json, ignore_unknown_fields=True)
|
|
207
|
+
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
def _read_auth_string(self, metadata: typing.Tuple) -> str:
|
|
211
|
+
"""
|
|
212
|
+
The auth string returned is either an API key or a PAT (Personal Access Token).
|
|
213
|
+
:param metadata: The call metadata.
|
|
214
|
+
:return: The auth string.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
authorization_values = [v for k, v in metadata if k.lower() == "authorization"]
|
|
218
|
+
if (
|
|
219
|
+
len(authorization_values) != 1
|
|
220
|
+
or not authorization_values[0].startswith("Key ")
|
|
221
|
+
or len(authorization_values[0].split(" ")) != 2
|
|
222
|
+
):
|
|
223
|
+
raise UsageError(
|
|
224
|
+
"Please provide metadata with the format of "
|
|
225
|
+
"(('authorization', 'Key YOUR_CLARIFAI_API_KEY'),)"
|
|
226
|
+
)
|
|
227
|
+
api_key = authorization_values[0].split(" ")[1]
|
|
228
|
+
return api_key
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class JSONStreamStream(object):
|
|
232
|
+
"""This mimics the stream_stream calls and is actually the thing doing the http requests."""
|
|
233
|
+
|
|
234
|
+
def __init__(
|
|
235
|
+
self,
|
|
236
|
+
session, # type: requests.Session
|
|
237
|
+
request_message_descriptor, # type: Descriptor
|
|
238
|
+
resources, # type: typing.List[typing.Tuple[str, typing.Any]]
|
|
239
|
+
request_serializer, # type: typing.Callable
|
|
240
|
+
response_deserializer, # type: typing.Callable
|
|
195
241
|
):
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
242
|
+
# type: (...) -> None
|
|
243
|
+
"""
|
|
244
|
+
Args:
|
|
245
|
+
session: a request session
|
|
246
|
+
request_message_descriptor: this is a MessageDescriptor for the input type.
|
|
247
|
+
resources: a list of available resource endpoints
|
|
248
|
+
request_serializer: the method to use to serialize the request proto
|
|
249
|
+
response_deserializer: the response proto deserializer which will be used to convert the http
|
|
250
|
+
response will be parsed into this.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
response: a proto object of class response_deserializer filled in with the response.
|
|
254
|
+
"""
|
|
255
|
+
self.session = session
|
|
256
|
+
self.request_message_descriptor = request_message_descriptor
|
|
257
|
+
self.resources = resources
|
|
258
|
+
self.request_serializer = request_serializer
|
|
259
|
+
self.response_deserializer = response_deserializer
|
|
260
|
+
|
|
261
|
+
def __call__(self, request, metadata=None): # type: (Message, tuple) -> Message
|
|
262
|
+
"""This is where the actually calls come through when the stub is called such as
|
|
263
|
+
stub.PostInputs(). They get passed to this method which actually makes the request.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
request: the proto object for the request. It must be the proper type for the request or the
|
|
267
|
+
server will complain. Note: this doesn't type check the incoming request in the client but
|
|
268
|
+
does make sure it can serialize before sending to the server atleast.
|
|
269
|
+
metadata: the authorization string (either API key or Personal Access Token)
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
response: the proto object that this method returns.
|
|
273
|
+
"""
|
|
274
|
+
# if metadata is not None:
|
|
275
|
+
# raise Exception("No support currently for metadata field.")
|
|
276
|
+
|
|
277
|
+
# There is no __self__ attribute on the request_serializer unfortunately.
|
|
278
|
+
expected_object_name = self.request_message_descriptor.name
|
|
279
|
+
if type(request).__name__ != expected_object_name:
|
|
280
|
+
raise Exception(
|
|
281
|
+
"The input request must be of type: %s from %s"
|
|
282
|
+
% (expected_object_name, self.request_message_descriptor.file.name)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
params = protobuf_to_dict(request, use_integers_for_enums=False, ignore_show_empty=True)
|
|
286
|
+
|
|
287
|
+
url, method, url_fields = _pick_proper_endpoint(self.resources, params)
|
|
288
|
+
|
|
289
|
+
for url_field in url_fields:
|
|
290
|
+
if url_field in params:
|
|
291
|
+
del params[url_field]
|
|
292
|
+
|
|
293
|
+
auth_string = self._read_auth_string(metadata)
|
|
294
|
+
|
|
295
|
+
http = http_client.HttpClient(self.session, auth_string)
|
|
296
|
+
response_json = http.execute_request(method, params, url)
|
|
297
|
+
|
|
298
|
+
# Get the actual message object to construct
|
|
299
|
+
message = self.response_deserializer
|
|
300
|
+
result = dict_to_protobuf(message, response_json, ignore_unknown_fields=True)
|
|
301
|
+
|
|
302
|
+
return result
|
|
303
|
+
|
|
304
|
+
def _read_auth_string(self, metadata: typing.Tuple) -> str:
|
|
305
|
+
"""
|
|
306
|
+
The auth string returned is either an API key or a PAT (Personal Access Token).
|
|
307
|
+
:param metadata: The call metadata.
|
|
308
|
+
:return: The auth string.
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
authorization_values = [v for k, v in metadata if k.lower() == "authorization"]
|
|
312
|
+
if (
|
|
313
|
+
len(authorization_values) != 1
|
|
314
|
+
or not authorization_values[0].startswith("Key ")
|
|
315
|
+
or len(authorization_values[0].split(" ")) != 2
|
|
316
|
+
):
|
|
317
|
+
raise UsageError(
|
|
318
|
+
"Please provide metadata with the format of "
|
|
319
|
+
"(('authorization', 'Key YOUR_CLARIFAI_API_KEY'),)"
|
|
320
|
+
)
|
|
321
|
+
api_key = authorization_values[0].split(" ")[1]
|
|
322
|
+
return api_key
|
|
200
323
|
|
|
201
324
|
|
|
202
325
|
def _read_app_info(data):
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
326
|
+
"""
|
|
327
|
+
This function extracts the app_id and user_id values from the request object, or returns None.
|
|
328
|
+
:param data: The request object
|
|
329
|
+
:return: (app_id, user_id) or None
|
|
330
|
+
"""
|
|
331
|
+
if type(data) is list:
|
|
332
|
+
for e in data:
|
|
333
|
+
vals = _read_app_info(e)
|
|
334
|
+
if vals:
|
|
335
|
+
return vals
|
|
336
|
+
elif type(data) is dict:
|
|
337
|
+
for k, v in data.items():
|
|
338
|
+
if k == "user_app_id":
|
|
339
|
+
return v.get("app_id", ""), v.get("user_id", "me")
|
|
340
|
+
elif k == "apps":
|
|
341
|
+
if len(v) == 1:
|
|
342
|
+
return v[0]["id"], v[0].get("user_id", "me")
|
|
343
|
+
elif len(v) == 0:
|
|
344
|
+
return None
|
|
345
|
+
else:
|
|
346
|
+
raise ClarifaiException("Only one app has to be specified")
|
|
347
|
+
elif k == "metadata":
|
|
348
|
+
continue
|
|
349
|
+
vals = _read_app_info(v)
|
|
350
|
+
if vals:
|
|
351
|
+
return vals
|
|
352
|
+
return None
|
|
228
353
|
|
|
229
354
|
|
|
230
355
|
def _pick_proper_endpoint(
|
|
231
356
|
resources, # type: typing.List[typing.Tuple[str, typing.Any]]
|
|
232
|
-
request_dict # type: dict
|
|
357
|
+
request_dict, # type: dict
|
|
233
358
|
):
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
359
|
+
# type: (...) -> typing.Tuple[str, typing.Any, typing.List[str]]
|
|
360
|
+
"""
|
|
361
|
+
Fills in the url template with the actual url params from the request body.
|
|
362
|
+
Picks the most appropriate url depending on which parameters are present in the request body.
|
|
363
|
+
Args:
|
|
364
|
+
resources: all available resource endpoints for this method.
|
|
365
|
+
request_dict: a dictionary form of the request from json_format.MessageToDict(request,
|
|
366
|
+
preserving_proto_field_name=True) so that we can recursively lookup url params.
|
|
367
|
+
Returns:
|
|
368
|
+
url: the url string to use in requests.
|
|
369
|
+
method: one of get/post/patch/delete.
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
best_match_url = None
|
|
373
|
+
best_match_method = None
|
|
374
|
+
best_match_count = -1
|
|
375
|
+
|
|
376
|
+
ids = _read_app_info(request_dict)
|
|
377
|
+
app_id, user_id = ids if ids else (None, None)
|
|
378
|
+
|
|
379
|
+
all_fields = []
|
|
380
|
+
best_match_url_fields = None
|
|
381
|
+
for url_template, method in resources:
|
|
382
|
+
all_arguments_translated = True
|
|
383
|
+
|
|
384
|
+
url = url_template
|
|
385
|
+
count = 0
|
|
386
|
+
url_fields = list(re.findall(URL_TEMPLATE_PARAM_REGEX, url_template))
|
|
387
|
+
for field in url_fields:
|
|
388
|
+
field_name = field.split(".")[-1]
|
|
389
|
+
|
|
390
|
+
if field_name == "app_id":
|
|
391
|
+
field_value = app_id
|
|
392
|
+
elif field_name == "user_id":
|
|
393
|
+
if user_id:
|
|
394
|
+
field_value = user_id
|
|
395
|
+
else:
|
|
396
|
+
# "me" is the alias for the ID of the authorized user.
|
|
397
|
+
field_value = "me"
|
|
398
|
+
else:
|
|
399
|
+
field_value = request_dict.get(field_name)
|
|
400
|
+
if not field_value:
|
|
401
|
+
all_arguments_translated = False
|
|
402
|
+
break
|
|
403
|
+
|
|
404
|
+
count += 1
|
|
405
|
+
|
|
406
|
+
url = url.replace("{" + field + "}", field_value)
|
|
407
|
+
|
|
408
|
+
all_fields.extend(url_fields)
|
|
409
|
+
|
|
410
|
+
if all_arguments_translated:
|
|
411
|
+
if best_match_count < count:
|
|
412
|
+
best_match_url = url
|
|
413
|
+
best_match_url_fields = url_fields
|
|
414
|
+
best_match_method = method
|
|
415
|
+
best_match_count = count
|
|
416
|
+
|
|
417
|
+
if not best_match_url:
|
|
418
|
+
raise Exception(
|
|
419
|
+
"You must set one case of the following fields in your request proto: %s" % all_fields
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
return best_match_url, best_match_method, best_match_url_fields
|