Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/grpc/aio/_metadata.py: 37%
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
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
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
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, List, Optional, Tuple, Union
29from typing_extensions import Self
31MetadataKey = str
32MetadataValue = Union[str, bytes]
33MetadatumType = Tuple[MetadataKey, MetadataValue]
34MetadataType = Union["Metadata", Sequence[MetadatumType]]
37class Metadata(Collection): # noqa: PLW1641
38 """Metadata abstraction for the asynchronous calls and interceptors.
40 The metadata is a mapping from str -> List[str]
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 """
50 def __init__(self, *args: MetadatumType) -> None:
51 self._metadata = OrderedDict()
52 for md_key, md_value in args:
53 self.add(md_key, md_value)
55 @classmethod
56 def from_tuple(cls, raw_metadata: tuple):
57 # Note: We unintentionally support non-tuple arguments here. We plan
58 # to emit a DeprecationWarning when a non-tuple type is used.
59 if raw_metadata:
60 return cls(*raw_metadata)
61 return cls()
63 @classmethod
64 def _create(
65 cls,
66 raw_metadata: Union[None, Self, Iterable[MetadatumType]],
67 ) -> Self:
68 # TODO(asheshvidyut): Make this method public and encourage people to use it instead
69 # of `from_tuple` to create metadata from non-tuple types.
70 if raw_metadata is None:
71 return cls()
72 if isinstance(raw_metadata, cls):
73 return raw_metadata
74 if raw_metadata:
75 return cls(*raw_metadata)
76 return cls()
78 def add(self, key: MetadataKey, value: MetadataValue) -> None:
79 self._metadata.setdefault(key, [])
80 self._metadata[key].append(value)
82 def __len__(self) -> int:
83 """Return the total number of elements that there are in the metadata,
84 including multiple values for the same key.
85 """
86 return sum(map(len, self._metadata.values()))
88 def __getitem__(self, key: MetadataKey) -> MetadataValue:
89 """When calling <metadata>[<key>], the first element of all those
90 mapped for <key> is returned.
91 """
92 try:
93 return self._metadata[key][0]
94 except (ValueError, IndexError) as e:
95 error_msg = f"{key!r}"
96 raise KeyError(error_msg) from e
98 def __setitem__(self, key: MetadataKey, value: MetadataValue) -> None:
99 """Calling metadata[<key>] = <value>
100 Maps <value> to the first instance of <key>.
101 """
102 if key not in self:
103 self._metadata[key] = [value]
104 else:
105 current_values = self.get_all(key)
106 self._metadata[key] = [value, *current_values[1:]]
108 def __delitem__(self, key: MetadataKey) -> None:
109 """``del metadata[<key>]`` deletes the first mapping for <key>."""
110 current_values = self.get_all(key)
111 if not current_values:
112 raise KeyError(repr(key))
113 self._metadata[key] = current_values[1:]
115 def delete_all(self, key: MetadataKey) -> None:
116 """Delete all mappings for <key>."""
117 del self._metadata[key]
119 def __iter__(self) -> Iterator[Tuple[MetadataKey, MetadataValue]]:
120 for key, values in self._metadata.items():
121 for value in values:
122 yield (key, value)
124 def keys(self) -> KeysView:
125 return KeysView(self._metadata)
127 def values(self) -> ValuesView:
128 return ValuesView(self._metadata)
130 def items(self) -> ItemsView:
131 return ItemsView(self._metadata)
133 def get(
134 self, key: MetadataKey, default: Optional[MetadataValue] = None
135 ) -> Optional[MetadataValue]:
136 try:
137 return self[key]
138 except KeyError:
139 return default
141 def get_all(self, key: MetadataKey) -> List[MetadataValue]:
142 """For compatibility with other Metadata abstraction objects (like in Java),
143 this would return all items under the desired <key>.
144 """
145 return self._metadata.get(key, [])
147 def set_all(self, key: MetadataKey, values: List[MetadataValue]) -> None:
148 self._metadata[key] = values
150 def __contains__(self, key: MetadataKey) -> bool:
151 return key in self._metadata
153 def __eq__(self, other: object) -> bool:
154 if isinstance(other, self.__class__):
155 return self._metadata == other._metadata
156 if isinstance(other, tuple):
157 return tuple(self) == other
158 return NotImplemented # pytype: disable=bad-return-type
160 def __add__(self, other: Any) -> "Metadata":
161 if isinstance(other, self.__class__):
162 return Metadata(*(tuple(self) + tuple(other)))
163 if isinstance(other, tuple):
164 return Metadata(*(tuple(self) + other))
165 return NotImplemented # pytype: disable=bad-return-type
167 def __repr__(self) -> str:
168 view = tuple(self)
169 return "{0}({1!r})".format(self.__class__.__name__, view)