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