2525
2626from __future__ import unicode_literals
2727
28+ from collections import deque
29+ import copy
2830import functools
2931import re
3032
@@ -64,7 +66,7 @@ def _expand_variable_match(positional_vars, named_vars, match):
6466 """Expand a matched variable with its value.
6567
6668 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
6870 be modified.
6971 named_vars (dict): A dictionary of named variables.
7072 match (re.Match): A regular expression match.
@@ -170,6 +172,46 @@ def _generate_pattern_for_template(tmpl):
170172 return _VARIABLE_RE .sub (_replace_variable_with_pattern , tmpl )
171173
172174
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+
173215def validate (tmpl , path ):
174216 """Validate a path against the path template.
175217
@@ -193,3 +235,66 @@ def validate(tmpl, path):
193235 """
194236 pattern = _generate_pattern_for_template (tmpl ) + "$"
195237 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