| # Copyright 2021 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Helpers for rest transports.""" |
| |
| import functools |
| import operator |
| |
| |
| def flatten_query_params(obj): |
| """Flatten a nested dict into a list of (name,value) tuples. |
| |
| The result is suitable for setting query params on an http request. |
| |
| .. code-block:: python |
| |
| >>> 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')] |
| |
| 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 |
| |
| Returns: a list of tuples, with each tuple having a (possibly) multi-part name |
| and a scalar value. |
| |
| Raises: |
| TypeError if obj is not a dict or None |
| ValueError if obj contains a list of non-primitive values. |
| """ |
| |
| 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=[]) |
| |
| |
| def _flatten(obj, key_path): |
| if obj is None: |
| return [] |
| if isinstance(obj, dict): |
| return _flatten_dict(obj, key_path=key_path) |
| if isinstance(obj, list): |
| return _flatten_list(obj, key_path=key_path) |
| return _flatten_value(obj, key_path=key_path) |
| |
| |
| def _is_primitive_value(obj): |
| if obj is None: |
| return False |
| |
| if isinstance(obj, (list, dict)): |
| raise ValueError("query params may not contain repeated dicts or lists") |
| |
| return True |
| |
| |
| def _flatten_value(obj, key_path): |
| return [(".".join(key_path), obj)] |
| |
| |
| def _flatten_dict(obj, key_path): |
| items = (_flatten(value, key_path=key_path + [key]) for key, value in obj.items()) |
| return functools.reduce(operator.concat, items, []) |
| |
| |
| def _flatten_list(elems, key_path): |
| # Only lists of scalar values are supported. |
| # The name (key_path) is repeated for each value. |
| items = ( |
| _flatten_value(elem, key_path=key_path) |
| for elem in elems |
| if _is_primitive_value(elem) |
| ) |
| return functools.reduce(operator.concat, items, []) |