1# Copyright 2020 gRPC authors. 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"""Implementation of the metadata abstraction for gRPC Asyncio Python.""" 15from typing import List, Tuple, Iterator, Any, Union 16from collections import abc, OrderedDict 17 18MetadataKey = str 19MetadataValue = Union[str, bytes] 20 21 22class Metadata(abc.Mapping): 23 """Metadata abstraction for the asynchronous calls and interceptors. 24 25 The metadata is a mapping from str -> List[str] 26 27 Traits 28 * Multiple entries are allowed for the same key 29 * The order of the values by key is preserved 30 * Getting by an element by key, retrieves the first mapped value 31 * Supports an immutable view of the data 32 * Allows partial mutation on the data without recreating the new object from scratch. 33 """ 34 35 def __init__(self, *args: Tuple[MetadataKey, MetadataValue]) -> None: 36 self._metadata = OrderedDict() 37 for md_key, md_value in args: 38 self.add(md_key, md_value) 39 40 @classmethod 41 def from_tuple(cls, raw_metadata: tuple): 42 if raw_metadata: 43 return cls(*raw_metadata) 44 return cls() 45 46 def add(self, key: MetadataKey, value: MetadataValue) -> None: 47 self._metadata.setdefault(key, []) 48 self._metadata[key].append(value) 49 50 def __len__(self) -> int: 51 """Return the total number of elements that there are in the metadata, 52 including multiple values for the same key. 53 """ 54 return sum(map(len, self._metadata.values())) 55 56 def __getitem__(self, key: MetadataKey) -> MetadataValue: 57 """When calling <metadata>[<key>], the first element of all those 58 mapped for <key> is returned. 59 """ 60 try: 61 return self._metadata[key][0] 62 except (ValueError, IndexError) as e: 63 raise KeyError("{0!r}".format(key)) from e 64 65 def __setitem__(self, key: MetadataKey, value: MetadataValue) -> None: 66 """Calling metadata[<key>] = <value> 67 Maps <value> to the first instance of <key>. 68 """ 69 if key not in self: 70 self._metadata[key] = [value] 71 else: 72 current_values = self.get_all(key) 73 self._metadata[key] = [value, *current_values[1:]] 74 75 def __delitem__(self, key: MetadataKey) -> None: 76 """``del metadata[<key>]`` deletes the first mapping for <key>.""" 77 current_values = self.get_all(key) 78 if not current_values: 79 raise KeyError(repr(key)) 80 self._metadata[key] = current_values[1:] 81 82 def delete_all(self, key: MetadataKey) -> None: 83 """Delete all mappings for <key>.""" 84 del self._metadata[key] 85 86 def __iter__(self) -> Iterator[Tuple[MetadataKey, MetadataValue]]: 87 for key, values in self._metadata.items(): 88 for value in values: 89 yield (key, value) 90 91 def get_all(self, key: MetadataKey) -> List[MetadataValue]: 92 """For compatibility with other Metadata abstraction objects (like in Java), 93 this would return all items under the desired <key>. 94 """ 95 return self._metadata.get(key, []) 96 97 def set_all(self, key: MetadataKey, values: List[MetadataValue]) -> None: 98 self._metadata[key] = values 99 100 def __contains__(self, key: MetadataKey) -> bool: 101 return key in self._metadata 102 103 def __eq__(self, other: Any) -> bool: 104 if isinstance(other, self.__class__): 105 return self._metadata == other._metadata 106 if isinstance(other, tuple): 107 return tuple(self) == other 108 return NotImplemented # pytype: disable=bad-return-type 109 110 def __add__(self, other: Any) -> 'Metadata': 111 if isinstance(other, self.__class__): 112 return Metadata(*(tuple(self) + tuple(other))) 113 if isinstance(other, tuple): 114 return Metadata(*(tuple(self) + other)) 115 return NotImplemented # pytype: disable=bad-return-type 116 117 def __repr__(self) -> str: 118 view = tuple(self) 119 return "{0}({1!r})".format(self.__class__.__name__, view) 120