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
16
17from proto.utils import cached_property
18
19
20class MapComposite(collections.abc.MutableMapping):
21 """A view around a mutable sequence in protocol buffers.
22
23 This implements the full Python MutableMapping interface, but all methods
24 modify the underlying field container directly.
25 """
26
27 @cached_property
28 def _pb_type(self):
29 """Return the protocol buffer type for this sequence."""
30 # Huzzah, another hack. Still less bad than RepeatedComposite.
31 return type(self.pb.GetEntryClass()().value)
32
33 def __init__(self, sequence, *, marshal):
34 """Initialize a wrapper around a protobuf map.
35
36 Args:
37 sequence: A protocol buffers map.
38 marshal (~.MarshalRegistry): An instantiated marshal, used to
39 convert values going to and from this map.
40 """
41 self._pb = sequence
42 self._marshal = marshal
43
44 def __contains__(self, key):
45 # Protocol buffers is so permissive that querying for the existence
46 # of a key will in of itself create it.
47 #
48 # By taking a tuple of the keys and querying that, we avoid sending
49 # the lookup to protocol buffers and therefore avoid creating the key.
50 return key in tuple(self.keys())
51
52 def __getitem__(self, key):
53 # We handle raising KeyError ourselves, because otherwise protocol
54 # buffers will create the key if it does not exist.
55 if key not in self:
56 raise KeyError(key)
57 return self._marshal.to_python(self._pb_type, self.pb[key])
58
59 def __setitem__(self, key, value):
60 pb_value = self._marshal.to_proto(self._pb_type, value, strict=True)
61 # Directly setting a key is not allowed; however, protocol buffers
62 # is so permissive that querying for the existence of a key will in
63 # of itself create it.
64 #
65 # Therefore, we create a key that way (clearing any fields that may
66 # be set) and then merge in our values.
67 self.pb[key].Clear()
68 self.pb[key].MergeFrom(pb_value)
69
70 def __delitem__(self, key):
71 self.pb.pop(key)
72
73 def __len__(self):
74 return len(self.pb)
75
76 def __iter__(self):
77 return iter(self.pb)
78
79 @property
80 def pb(self):
81 return self._pb