25
25
26
26
from __future__ import unicode_literals
27
27
28
+ from collections import deque
29
+ import copy
28
30
import functools
29
31
import re
30
32
@@ -64,7 +66,7 @@ def _expand_variable_match(positional_vars, named_vars, match):
64
66
"""Expand a matched variable with its value.
65
67
66
68
Args:
67
- positional_vars (list): A list of positonal variables. This list will
69
+ positional_vars (list): A list of positional variables. This list will
68
70
be modified.
69
71
named_vars (dict): A dictionary of named variables.
70
72
match (re.Match): A regular expression match.
@@ -170,6 +172,46 @@ def _generate_pattern_for_template(tmpl):
170
172
return _VARIABLE_RE .sub (_replace_variable_with_pattern , tmpl )
171
173
172
174
175
+ def get_field (request , field ):
176
+ """Get the value of a field from a given dictionary.
177
+
178
+ Args:
179
+ request (dict): A dictionary object.
180
+ field (str): The key to the request in dot notation.
181
+
182
+ Returns:
183
+ The value of the field.
184
+ """
185
+ parts = field .split ("." )
186
+ value = request
187
+ for part in parts :
188
+ if not isinstance (value , dict ):
189
+ return
190
+ value = value .get (part )
191
+ if isinstance (value , dict ):
192
+ return
193
+ return value
194
+
195
+
196
+ def delete_field (request , field ):
197
+ """Delete the value of a field from a given dictionary.
198
+
199
+ Args:
200
+ request (dict): A dictionary object.
201
+ field (str): The key to the request in dot notation.
202
+ """
203
+ parts = deque (field .split ("." ))
204
+ while len (parts ) > 1 :
205
+ if not isinstance (request , dict ):
206
+ return
207
+ part = parts .popleft ()
208
+ request = request .get (part )
209
+ part = parts .popleft ()
210
+ if not isinstance (request , dict ):
211
+ return
212
+ request .pop (part , None )
213
+
214
+
173
215
def validate (tmpl , path ):
174
216
"""Validate a path against the path template.
175
217
@@ -193,3 +235,66 @@ def validate(tmpl, path):
193
235
"""
194
236
pattern = _generate_pattern_for_template (tmpl ) + "$"
195
237
return True if re .match (pattern , path ) is not None else False
238
+
239
+
240
+ def transcode (http_options , ** request_kwargs ):
241
+ """Transcodes a grpc request pattern into a proper HTTP request following the rules outlined here,
242
+ https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L44-L312
243
+
244
+ Args:
245
+ http_options (list(dict)): A list of dicts which consist of these keys,
246
+ 'method' (str): The http method
247
+ 'uri' (str): The path template
248
+ 'body' (str): The body field name (optional)
249
+ (This is a simplified representation of the proto option `google.api.http`)
250
+
251
+ request_kwargs (dict) : A dict representing the request object
252
+
253
+ Returns:
254
+ dict: The transcoded request with these keys,
255
+ 'method' (str) : The http method
256
+ 'uri' (str) : The expanded uri
257
+ 'body' (dict) : A dict representing the body (optional)
258
+ 'query_params' (dict) : A dict mapping query parameter variables and values
259
+
260
+ Raises:
261
+ ValueError: If the request does not match the given template.
262
+ """
263
+ for http_option in http_options :
264
+ request = {}
265
+
266
+ # Assign path
267
+ uri_template = http_option ["uri" ]
268
+ path_fields = [
269
+ match .group ("name" ) for match in _VARIABLE_RE .finditer (uri_template )
270
+ ]
271
+ path_args = {field : get_field (request_kwargs , field ) for field in path_fields }
272
+ request ["uri" ] = expand (uri_template , ** path_args )
273
+
274
+ # Remove fields used in uri path from request
275
+ leftovers = copy .deepcopy (request_kwargs )
276
+ for path_field in path_fields :
277
+ delete_field (leftovers , path_field )
278
+
279
+ if not validate (uri_template , request ["uri" ]) or not all (path_args .values ()):
280
+ continue
281
+
282
+ # Assign body and query params
283
+ body = http_option .get ("body" )
284
+
285
+ if body :
286
+ if body == "*" :
287
+ request ["body" ] = leftovers
288
+ request ["query_params" ] = {}
289
+ else :
290
+ try :
291
+ request ["body" ] = leftovers .pop (body )
292
+ except KeyError :
293
+ continue
294
+ request ["query_params" ] = leftovers
295
+ else :
296
+ request ["query_params" ] = leftovers
297
+ request ["method" ] = http_option ["method" ]
298
+ return request
299
+
300
+ raise ValueError ("Request obj does not match any template" )
0 commit comments