diff --git a/google/api_core/rest_helpers.py b/google/api_core/rest_helpers.py index 23fb614f..a78822f1 100644 --- a/google/api_core/rest_helpers.py +++ b/google/api_core/rest_helpers.py @@ -18,8 +18,8 @@ import operator -def flatten_query_params(obj): - """Flatten a nested dict into a list of (name,value) tuples. +def flatten_query_params(obj, strict=False): + """Flatten a dict into a list of (name,value) tuples. The result is suitable for setting query params on an http request. @@ -28,9 +28,10 @@ def flatten_query_params(obj): >>> obj = {'a': ... {'b': ... {'c': ['x', 'y', 'z']} }, - ... 'd': 'uvw', } - >>> flatten_query_params(obj) - [('a.b.c', 'x'), ('a.b.c', 'y'), ('a.b.c', 'z'), ('d', 'uvw')] + ... 'd': 'uvw', + ... 'e': True, } + >>> flatten_query_params(obj, strict=True) + [('a.b.c', 'x'), ('a.b.c', 'y'), ('a.b.c', 'z'), ('d', 'uvw'), ('e', 'true')] Note that, as described in https://github.com/googleapis/googleapis/blob/48d9fb8c8e287c472af500221c6450ecd45d7d39/google/api/http.proto#L117, @@ -38,7 +39,9 @@ def flatten_query_params(obj): This is enforced in this function. Args: - obj: a nested dictionary (from json), or None + obj: a possibly nested dictionary (from json), or None + strict: a bool, defaulting to False, to enforce that all values in the + result tuples be strings and, if boolean, lower-cased. Returns: a list of tuples, with each tuple having a (possibly) multi-part name and a scalar value. @@ -51,17 +54,17 @@ def flatten_query_params(obj): if obj is not None and not isinstance(obj, dict): raise TypeError("flatten_query_params must be called with dict object") - return _flatten(obj, key_path=[]) + return _flatten(obj, key_path=[], strict=strict) -def _flatten(obj, key_path): +def _flatten(obj, key_path, strict=False): if obj is None: return [] if isinstance(obj, dict): - return _flatten_dict(obj, key_path=key_path) + return _flatten_dict(obj, key_path=key_path, strict=strict) if isinstance(obj, list): - return _flatten_list(obj, key_path=key_path) - return _flatten_value(obj, key_path=key_path) + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) def _is_primitive_value(obj): @@ -74,21 +77,33 @@ def _is_primitive_value(obj): return True -def _flatten_value(obj, key_path): - return [(".".join(key_path), obj)] +def _flatten_value(obj, key_path, strict=False): + return [(".".join(key_path), _canonicalize(obj, strict=strict))] -def _flatten_dict(obj, key_path): - items = (_flatten(value, key_path=key_path + [key]) for key, value in obj.items()) +def _flatten_dict(obj, key_path, strict=False): + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) return functools.reduce(operator.concat, items, []) -def _flatten_list(elems, key_path): +def _flatten_list(elems, key_path, strict=False): # Only lists of scalar values are supported. # The name (key_path) is repeated for each value. items = ( - _flatten_value(elem, key_path=key_path) + _flatten_value(elem, key_path=key_path, strict=strict) for elem in elems if _is_primitive_value(elem) ) return functools.reduce(operator.concat, items, []) + + +def _canonicalize(obj, strict=False): + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj diff --git a/tests/unit/test_rest_helpers.py b/tests/unit/test_rest_helpers.py index 5932fa55..ff1a43f0 100644 --- a/tests/unit/test_rest_helpers.py +++ b/tests/unit/test_rest_helpers.py @@ -36,9 +36,26 @@ def test_flatten_empty_dict(): def test_flatten_simple_dict(): - assert rest_helpers.flatten_query_params({"a": "abc", "b": "def"}) == [ + obj = {"a": "abc", "b": "def", "c": True, "d": False, "e": 10, "f": -3.76} + assert rest_helpers.flatten_query_params(obj) == [ ("a", "abc"), ("b", "def"), + ("c", True), + ("d", False), + ("e", 10), + ("f", -3.76), + ] + + +def test_flatten_simple_dict_strict(): + obj = {"a": "abc", "b": "def", "c": True, "d": False, "e": 10, "f": -3.76} + assert rest_helpers.flatten_query_params(obj, strict=True) == [ + ("a", "abc"), + ("b", "def"), + ("c", "true"), + ("d", "false"), + ("e", "10"), + ("f", "-3.76"), ]