Skip to content

Commit

Permalink
feat: add grpc transcoding + tests (#259)
Browse files Browse the repository at this point in the history
* 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 <yonmg@google.com>
Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
Co-authored-by: Tres Seaver <tseaver@palladion.com>
  • Loading branch information
4 people committed Sep 15, 2021
1 parent 8b65c93 commit afe0fa1
Show file tree
Hide file tree
Showing 2 changed files with 380 additions and 1 deletion.
107 changes: 106 additions & 1 deletion google/api_core/path_template.py
Expand Up @@ -25,6 +25,8 @@

from __future__ import unicode_literals

from collections import deque
import copy
import functools
import re

Expand Down Expand Up @@ -64,7 +66,7 @@ def _expand_variable_match(positional_vars, named_vars, match):
"""Expand a matched variable with its value.
Args:
positional_vars (list): A list of positonal variables. This list will
positional_vars (list): A list of positional variables. This list will
be modified.
named_vars (dict): A dictionary of named variables.
match (re.Match): A regular expression match.
Expand Down Expand Up @@ -170,6 +172,46 @@ def _generate_pattern_for_template(tmpl):
return _VARIABLE_RE.sub(_replace_variable_with_pattern, tmpl)


def get_field(request, field):
"""Get the value of a field from a given dictionary.
Args:
request (dict): A dictionary object.
field (str): The key to the request in dot notation.
Returns:
The value of the field.
"""
parts = field.split(".")
value = request
for part in parts:
if not isinstance(value, dict):
return
value = value.get(part)
if isinstance(value, dict):
return
return value


def delete_field(request, field):
"""Delete the value of a field from a given dictionary.
Args:
request (dict): A dictionary object.
field (str): The key to the request in dot notation.
"""
parts = deque(field.split("."))
while len(parts) > 1:
if not isinstance(request, dict):
return
part = parts.popleft()
request = request.get(part)
part = parts.popleft()
if not isinstance(request, dict):
return
request.pop(part, None)


def validate(tmpl, path):
"""Validate a path against the path template.
Expand All @@ -193,3 +235,66 @@ def validate(tmpl, path):
"""
pattern = _generate_pattern_for_template(tmpl) + "$"
return True if re.match(pattern, path) is not None else False


def transcode(http_options, **request_kwargs):
"""Transcodes a grpc request pattern into a proper HTTP request following the rules outlined here,
https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L44-L312
Args:
http_options (list(dict)): A list of dicts which consist of these keys,
'method' (str): The http method
'uri' (str): The path template
'body' (str): The body field name (optional)
(This is a simplified representation of the proto option `google.api.http`)
request_kwargs (dict) : A dict representing the request object
Returns:
dict: The transcoded request with these keys,
'method' (str) : The http method
'uri' (str) : The expanded uri
'body' (dict) : A dict representing the body (optional)
'query_params' (dict) : A dict mapping query parameter variables and values
Raises:
ValueError: If the request does not match the given template.
"""
for http_option in http_options:
request = {}

# Assign path
uri_template = http_option["uri"]
path_fields = [
match.group("name") for match in _VARIABLE_RE.finditer(uri_template)
]
path_args = {field: get_field(request_kwargs, field) for field in path_fields}
request["uri"] = expand(uri_template, **path_args)

# Remove fields used in uri path from request
leftovers = copy.deepcopy(request_kwargs)
for path_field in path_fields:
delete_field(leftovers, path_field)

if not validate(uri_template, request["uri"]) or not all(path_args.values()):
continue

# Assign body and query params
body = http_option.get("body")

if body:
if body == "*":
request["body"] = leftovers
request["query_params"] = {}
else:
try:
request["body"] = leftovers.pop(body)
except KeyError:
continue
request["query_params"] = leftovers
else:
request["query_params"] = leftovers
request["method"] = http_option["method"]
return request

raise ValueError("Request obj does not match any template")

0 comments on commit afe0fa1

Please sign in to comment.