1# Copyright 2021 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Helpers for rest transports.""" 16 17import functools 18import operator 19 20 21def flatten_query_params(obj): 22 """Flatten a nested dict into a list of (name,value) tuples. 23 24 The result is suitable for setting query params on an http request. 25 26 .. code-block:: python 27 28 >>> obj = {'a': 29 ... {'b': 30 ... {'c': ['x', 'y', 'z']} }, 31 ... 'd': 'uvw', } 32 >>> flatten_query_params(obj) 33 [('a.b.c', 'x'), ('a.b.c', 'y'), ('a.b.c', 'z'), ('d', 'uvw')] 34 35 Note that, as described in 36 https://github.com/googleapis/googleapis/blob/48d9fb8c8e287c472af500221c6450ecd45d7d39/google/api/http.proto#L117, 37 repeated fields (i.e. list-valued fields) may only contain primitive types (not lists or dicts). 38 This is enforced in this function. 39 40 Args: 41 obj: a nested dictionary (from json), or None 42 43 Returns: a list of tuples, with each tuple having a (possibly) multi-part name 44 and a scalar value. 45 46 Raises: 47 TypeError if obj is not a dict or None 48 ValueError if obj contains a list of non-primitive values. 49 """ 50 51 if obj is not None and not isinstance(obj, dict): 52 raise TypeError("flatten_query_params must be called with dict object") 53 54 return _flatten(obj, key_path=[]) 55 56 57def _flatten(obj, key_path): 58 if obj is None: 59 return [] 60 if isinstance(obj, dict): 61 return _flatten_dict(obj, key_path=key_path) 62 if isinstance(obj, list): 63 return _flatten_list(obj, key_path=key_path) 64 return _flatten_value(obj, key_path=key_path) 65 66 67def _is_primitive_value(obj): 68 if obj is None: 69 return False 70 71 if isinstance(obj, (list, dict)): 72 raise ValueError("query params may not contain repeated dicts or lists") 73 74 return True 75 76 77def _flatten_value(obj, key_path): 78 return [(".".join(key_path), obj)] 79 80 81def _flatten_dict(obj, key_path): 82 items = (_flatten(value, key_path=key_path + [key]) for key, value in obj.items()) 83 return functools.reduce(operator.concat, items, []) 84 85 86def _flatten_list(elems, key_path): 87 # Only lists of scalar values are supported. 88 # The name (key_path) is repeated for each value. 89 items = ( 90 _flatten_value(elem, key_path=key_path) 91 for elem in elems 92 if _is_primitive_value(elem) 93 ) 94 return functools.reduce(operator.concat, items, []) 95