Skip to content

Commit

Permalink
feat: add 'strict' to flatten_query_params to lower-case bools (#433)
Browse files Browse the repository at this point in the history
* feat: add 'strict' to flatten_query_params to lower-case bools

* pylint

Co-authored-by: Anthonios Partheniou <partheniou@google.com>
  • Loading branch information
vchudnov-g and parthea committed Sep 2, 2022
1 parent 9066ed4 commit 83678e9
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 18 deletions.
49 changes: 32 additions & 17 deletions google/api_core/rest_helpers.py
Expand Up @@ -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.
Expand All @@ -28,17 +28,20 @@ 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,
repeated fields (i.e. list-valued fields) may only contain primitive types (not lists or dicts).
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.
Expand All @@ -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):
Expand All @@ -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
19 changes: 18 additions & 1 deletion tests/unit/test_rest_helpers.py
Expand Up @@ -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"),
]


Expand Down

0 comments on commit 83678e9

Please sign in to comment.