Skip to content

Commit afe0fa1

Browse files
yihjenkuyon-mggcf-owl-bot[bot]tseaver
authored
feat: add grpc transcoding + tests (googleapis#259)
* feat: add grpc transcoding + tests * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore: tweak for clarity / idiomatic usage * chore: attempt to appease Sphinx * feat: add grpc transcoding + tests * Add functions to properly handle subfields * Add unit tests for get_field and delete_field. * Add function docstrings and incorporate correct native dict functions. * Add function docstrings and incorporate correct native dict functions. * Increase code coverage * Increase code coverage * Increase code coverage * Reformat files Co-authored-by: Yonatan Getahun <[email protected]> Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: Tres Seaver <[email protected]>
1 parent 8b65c93 commit afe0fa1

File tree

2 files changed

+380
-1
lines changed

2 files changed

+380
-1
lines changed

google/api_core/path_template.py

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
from __future__ import unicode_literals
2727

28+
from collections import deque
29+
import copy
2830
import functools
2931
import 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+
173215
def 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

Comments
 (0)