Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/proto/marshal/collections/repeated.py: 32%

76 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:57 +0000

1# Copyright 2018 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# https://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 

15import collections 

16import copy 

17 

18from proto.utils import cached_property 

19 

20 

21class Repeated(collections.abc.MutableSequence): 

22 """A view around a mutable sequence in protocol buffers. 

23 

24 This implements the full Python MutableSequence interface, but all methods 

25 modify the underlying field container directly. 

26 """ 

27 

28 def __init__(self, sequence, *, marshal, proto_type=None): 

29 """Initialize a wrapper around a protobuf repeated field. 

30 

31 Args: 

32 sequence: A protocol buffers repeated field. 

33 marshal (~.MarshalRegistry): An instantiated marshal, used to 

34 convert values going to and from this map. 

35 """ 

36 self._pb = sequence 

37 self._marshal = marshal 

38 self._proto_type = proto_type 

39 

40 def __copy__(self): 

41 """Copy this object and return the copy.""" 

42 return type(self)(self.pb[:], marshal=self._marshal) 

43 

44 def __delitem__(self, key): 

45 """Delete the given item.""" 

46 del self.pb[key] 

47 

48 def __eq__(self, other): 

49 if hasattr(other, "pb"): 

50 return tuple(self.pb) == tuple(other.pb) 

51 return tuple(self.pb) == tuple(other) 

52 

53 def __getitem__(self, key): 

54 """Return the given item.""" 

55 return self.pb[key] 

56 

57 def __len__(self): 

58 """Return the length of the sequence.""" 

59 return len(self.pb) 

60 

61 def __ne__(self, other): 

62 return not self == other 

63 

64 def __repr__(self): 

65 return repr([*self]) 

66 

67 def __setitem__(self, key, value): 

68 self.pb[key] = value 

69 

70 def insert(self, index: int, value): 

71 """Insert ``value`` in the sequence before ``index``.""" 

72 self.pb.insert(index, value) 

73 

74 def sort(self, *, key: str = None, reverse: bool = False): 

75 """Stable sort *IN PLACE*.""" 

76 self.pb.sort(key=key, reverse=reverse) 

77 

78 @property 

79 def pb(self): 

80 return self._pb 

81 

82 

83class RepeatedComposite(Repeated): 

84 """A view around a mutable sequence of messages in protocol buffers. 

85 

86 This implements the full Python MutableSequence interface, but all methods 

87 modify the underlying field container directly. 

88 """ 

89 

90 @cached_property 

91 def _pb_type(self): 

92 """Return the protocol buffer type for this sequence.""" 

93 # Provide the marshal-given proto_type, if any. 

94 # Used for RepeatedComposite of Enum. 

95 if self._proto_type is not None: 

96 return self._proto_type 

97 

98 # There is no public-interface mechanism to determine the type 

99 # of what should go in the list (and the C implementation seems to 

100 # have no exposed mechanism at all). 

101 # 

102 # If the list has members, use the existing list members to 

103 # determine the type. 

104 if len(self.pb) > 0: 

105 return type(self.pb[0]) 

106 

107 # We have no members in the list, so we get the type from the attributes. 

108 if hasattr(self.pb, "_message_descriptor") and hasattr( 

109 self.pb._message_descriptor, "_concrete_class" 

110 ): 

111 return self.pb._message_descriptor._concrete_class 

112 

113 # Fallback logic in case attributes are not available 

114 # In order to get the type, we create a throw-away copy and add a 

115 # blank member to it. 

116 canary = copy.deepcopy(self.pb).add() 

117 return type(canary) 

118 

119 def __eq__(self, other): 

120 if super().__eq__(other): 

121 return True 

122 return tuple([i for i in self]) == tuple(other) 

123 

124 def __getitem__(self, key): 

125 return self._marshal.to_python(self._pb_type, self.pb[key]) 

126 

127 def __setitem__(self, key, value): 

128 # The underlying protocol buffer does not define __setitem__, so we 

129 # have to implement all the operations on our own. 

130 

131 # If ``key`` is an integer, as in list[index] = value: 

132 if isinstance(key, int): 

133 if -len(self) <= key < len(self): 

134 self.pop(key) # Delete the old item. 

135 self.insert(key, value) # Insert the new item in its place. 

136 else: 

137 raise IndexError("list assignment index out of range") 

138 

139 # If ``key`` is a slice object, as in list[start:stop:step] = [values]: 

140 elif isinstance(key, slice): 

141 start, stop, step = key.indices(len(self)) 

142 

143 if not isinstance(value, collections.abc.Iterable): 

144 raise TypeError("can only assign an iterable") 

145 

146 if step == 1: # Is not an extended slice. 

147 # Assign all the new values to the sliced part, replacing the 

148 # old values, if any, and unconditionally inserting those 

149 # values whose indices already exceed the slice length. 

150 for index, item in enumerate(value): 

151 if start + index < stop: 

152 self.pop(start + index) 

153 self.insert(start + index, item) 

154 

155 # If there are less values than the length of the slice, remove 

156 # the remaining elements so that the slice adapts to the 

157 # newly provided values. 

158 for _ in range(stop - start - len(value)): 

159 self.pop(start + len(value)) 

160 

161 else: # Is an extended slice. 

162 indices = range(start, stop, step) 

163 

164 if len(value) != len(indices): # XXX: Use PEP 572 on 3.8+ 

165 raise ValueError( 

166 f"attempt to assign sequence of size " 

167 f"{len(value)} to extended slice of size " 

168 f"{len(indices)}" 

169 ) 

170 

171 # Assign each value to its index, calling this function again 

172 # with individual integer indexes that get processed above. 

173 for index, item in zip(indices, value): 

174 self[index] = item 

175 

176 else: 

177 raise TypeError( 

178 f"list indices must be integers or slices, not {type(key).__name__}" 

179 ) 

180 

181 def insert(self, index: int, value): 

182 """Insert ``value`` in the sequence before ``index``.""" 

183 pb_value = self._marshal.to_proto(self._pb_type, value) 

184 self.pb.insert(index, pb_value)