Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/grpc/aio/_metadata.py: 36%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

94 statements  

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 __future__ import annotations 

16 

17from collections import OrderedDict 

18from collections.abc import ( 

19 Collection, 

20 ItemsView, 

21 Iterable, 

22 Iterator, 

23 KeysView, 

24 Sequence, 

25 ValuesView, 

26) 

27from typing import Any, Optional, Tuple, Union 

28 

29from typing_extensions import Self 

30 

31MetadataKey = str 

32MetadataValue = Union[str, bytes] 

33MetadatumType = Tuple[MetadataKey, MetadataValue] 

34MetadataType = Union["Metadata", Sequence[MetadatumType]] 

35 

36 

37class Metadata(Collection[MetadatumType]): # noqa: PLW1641 

38 """Metadata abstraction for the asynchronous calls and interceptors. 

39 

40 The metadata is a mapping from str -> List[str] 

41 

42 Traits 

43 * Multiple entries are allowed for the same key 

44 * The order of the values by key is preserved 

45 * Getting by an element by key, retrieves the first mapped value 

46 * Supports an immutable view of the data 

47 * Allows partial mutation on the data without recreating the new object from scratch. 

48 """ 

49 

50 _metadata: OrderedDict[MetadataKey, list[MetadataValue]] 

51 

52 def __init__(self, *args: MetadatumType) -> None: 

53 self._metadata = OrderedDict() 

54 for md_key, md_value in args: 

55 self.add(md_key, md_value) 

56 

57 @classmethod 

58 def from_tuple(cls, raw_metadata: Iterable[MetadatumType]) -> Self: 

59 # Note: We unintentionally support non-tuple arguments here. We plan 

60 # to emit a DeprecationWarning when a non-tuple type is used. 

61 if raw_metadata: 

62 return cls(*raw_metadata) 

63 return cls() 

64 

65 @classmethod 

66 def _create( 

67 cls, 

68 raw_metadata: Union[None, Self, Iterable[MetadatumType]], 

69 ) -> Self: 

70 # TODO(asheshvidyut): Make this method public and encourage people to use it instead 

71 # of `from_tuple` to create metadata from non-tuple types. 

72 if raw_metadata is None: 

73 return cls() 

74 if isinstance(raw_metadata, cls): 

75 return raw_metadata 

76 if raw_metadata: 

77 return cls(*raw_metadata) 

78 return cls() 

79 

80 def add(self, key: MetadataKey, value: MetadataValue) -> None: 

81 self._metadata.setdefault(key, []) 

82 self._metadata[key].append(value) 

83 

84 def __len__(self) -> int: 

85 """Return the total number of elements that there are in the metadata, 

86 including multiple values for the same key. 

87 """ 

88 return sum(map(len, self._metadata.values())) 

89 

90 def __getitem__(self, key: MetadataKey) -> MetadataValue: 

91 """When calling <metadata>[<key>], the first element of all those 

92 mapped for <key> is returned. 

93 """ 

94 try: 

95 return self._metadata[key][0] 

96 except (ValueError, IndexError) as e: 

97 error_msg = f"{key!r}" 

98 raise KeyError(error_msg) from e 

99 

100 def __setitem__(self, key: MetadataKey, value: MetadataValue) -> None: 

101 """Calling metadata[<key>] = <value> 

102 Maps <value> to the first instance of <key>. 

103 """ 

104 if key not in self: 

105 self._metadata[key] = [value] 

106 else: 

107 current_values = self.get_all(key) 

108 self._metadata[key] = [value, *current_values[1:]] 

109 

110 def __delitem__(self, key: MetadataKey) -> None: 

111 """``del metadata[<key>]`` deletes the first mapping for <key>.""" 

112 # get_all() returns the live list stored in _metadata, not a copy, 

113 # so mutating it in place here also updates _metadata directly. 

114 values = self.get_all(key) 

115 if not values: 

116 raise KeyError(repr(key)) 

117 del values[0] 

118 if not values: 

119 del self._metadata[key] 

120 

121 def delete_all(self, key: MetadataKey) -> None: 

122 """Delete all mappings for <key>.""" 

123 del self._metadata[key] 

124 

125 def __iter__(self) -> Iterator[Tuple[MetadataKey, MetadataValue]]: 

126 for key, values in self._metadata.items(): 

127 for value in values: 

128 yield (key, value) 

129 

130 def keys(self) -> KeysView[MetadataKey]: 

131 return KeysView(self._metadata) 

132 

133 def values(self) -> ValuesView[list[MetadataValue]]: 

134 return ValuesView(self._metadata) 

135 

136 def items(self) -> ItemsView[MetadataKey, list[MetadataValue]]: 

137 return ItemsView(self._metadata) 

138 

139 def get( 

140 self, key: MetadataKey, default: Optional[MetadataValue] = None 

141 ) -> Optional[MetadataValue]: 

142 try: 

143 return self[key] 

144 except KeyError: 

145 return default 

146 

147 def get_all(self, key: MetadataKey) -> list[MetadataValue]: 

148 """For compatibility with other Metadata abstraction objects (like in Java), 

149 this would return all items under the desired <key>. 

150 """ 

151 return self._metadata.get(key, []) 

152 

153 def set_all(self, key: MetadataKey, values: list[MetadataValue]) -> None: 

154 self._metadata[key] = values 

155 

156 def __contains__(self, key: object) -> bool: 

157 if not isinstance(key, MetadataKey): 

158 return False 

159 return key in self._metadata 

160 

161 def __eq__(self, other: object) -> bool: 

162 if isinstance(other, self.__class__): 

163 return self._metadata == other._metadata 

164 if isinstance(other, tuple): 

165 return tuple(self) == other 

166 return NotImplemented # pytype: disable=bad-return-type 

167 

168 def __add__(self, other: Any) -> "Metadata": 

169 if isinstance(other, self.__class__): 

170 return Metadata(*(tuple(self) + tuple(other))) 

171 if isinstance(other, tuple): 

172 return Metadata(*(tuple(self) + other)) 

173 return NotImplemented # pytype: disable=bad-return-type 

174 

175 def __repr__(self) -> str: 

176 view = tuple(self) 

177 return "{0}({1!r})".format(self.__class__.__name__, view)