Brian Silverman | 9c614bc | 2016-02-15 20:20:02 -0500 | [diff] [blame^] | 1 | # Protocol Buffers - Google's data interchange format |
| 2 | # Copyright 2008 Google Inc. All rights reserved. |
| 3 | # https://developers.google.com/protocol-buffers/ |
| 4 | # |
| 5 | # Redistribution and use in source and binary forms, with or without |
| 6 | # modification, are permitted provided that the following conditions are |
| 7 | # met: |
| 8 | # |
| 9 | # * Redistributions of source code must retain the above copyright |
| 10 | # notice, this list of conditions and the following disclaimer. |
| 11 | # * Redistributions in binary form must reproduce the above |
| 12 | # copyright notice, this list of conditions and the following disclaimer |
| 13 | # in the documentation and/or other materials provided with the |
| 14 | # distribution. |
| 15 | # * Neither the name of Google Inc. nor the names of its |
| 16 | # contributors may be used to endorse or promote products derived from |
| 17 | # this software without specific prior written permission. |
| 18 | # |
| 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 22 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 23 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 24 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 25 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 26 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 27 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 | |
| 31 | """Contains container classes to represent different protocol buffer types. |
| 32 | |
| 33 | This file defines container classes which represent categories of protocol |
| 34 | buffer field types which need extra maintenance. Currently these categories |
| 35 | are: |
| 36 | - Repeated scalar fields - These are all repeated fields which aren't |
| 37 | composite (e.g. they are of simple types like int32, string, etc). |
| 38 | - Repeated composite fields - Repeated fields which are composite. This |
| 39 | includes groups and nested messages. |
| 40 | """ |
| 41 | |
| 42 | __author__ = 'petar@google.com (Petar Petrov)' |
| 43 | |
| 44 | import collections |
| 45 | import sys |
| 46 | |
| 47 | if sys.version_info[0] < 3: |
| 48 | # We would use collections.MutableMapping all the time, but in Python 2 it |
| 49 | # doesn't define __slots__. This causes two significant problems: |
| 50 | # |
| 51 | # 1. we can't disallow arbitrary attribute assignment, even if our derived |
| 52 | # classes *do* define __slots__. |
| 53 | # |
| 54 | # 2. we can't safely derive a C type from it without __slots__ defined (the |
| 55 | # interpreter expects to find a dict at tp_dictoffset, which we can't |
| 56 | # robustly provide. And we don't want an instance dict anyway. |
| 57 | # |
| 58 | # So this is the Python 2.7 definition of Mapping/MutableMapping functions |
| 59 | # verbatim, except that: |
| 60 | # 1. We declare __slots__. |
| 61 | # 2. We don't declare this as a virtual base class. The classes defined |
| 62 | # in collections are the interesting base classes, not us. |
| 63 | # |
| 64 | # Note: deriving from object is critical. It is the only thing that makes |
| 65 | # this a true type, allowing us to derive from it in C++ cleanly and making |
| 66 | # __slots__ properly disallow arbitrary element assignment. |
| 67 | |
| 68 | class Mapping(object): |
| 69 | __slots__ = () |
| 70 | |
| 71 | def get(self, key, default=None): |
| 72 | try: |
| 73 | return self[key] |
| 74 | except KeyError: |
| 75 | return default |
| 76 | |
| 77 | def __contains__(self, key): |
| 78 | try: |
| 79 | self[key] |
| 80 | except KeyError: |
| 81 | return False |
| 82 | else: |
| 83 | return True |
| 84 | |
| 85 | def iterkeys(self): |
| 86 | return iter(self) |
| 87 | |
| 88 | def itervalues(self): |
| 89 | for key in self: |
| 90 | yield self[key] |
| 91 | |
| 92 | def iteritems(self): |
| 93 | for key in self: |
| 94 | yield (key, self[key]) |
| 95 | |
| 96 | def keys(self): |
| 97 | return list(self) |
| 98 | |
| 99 | def items(self): |
| 100 | return [(key, self[key]) for key in self] |
| 101 | |
| 102 | def values(self): |
| 103 | return [self[key] for key in self] |
| 104 | |
| 105 | # Mappings are not hashable by default, but subclasses can change this |
| 106 | __hash__ = None |
| 107 | |
| 108 | def __eq__(self, other): |
| 109 | if not isinstance(other, collections.Mapping): |
| 110 | return NotImplemented |
| 111 | return dict(self.items()) == dict(other.items()) |
| 112 | |
| 113 | def __ne__(self, other): |
| 114 | return not (self == other) |
| 115 | |
| 116 | class MutableMapping(Mapping): |
| 117 | __slots__ = () |
| 118 | |
| 119 | __marker = object() |
| 120 | |
| 121 | def pop(self, key, default=__marker): |
| 122 | try: |
| 123 | value = self[key] |
| 124 | except KeyError: |
| 125 | if default is self.__marker: |
| 126 | raise |
| 127 | return default |
| 128 | else: |
| 129 | del self[key] |
| 130 | return value |
| 131 | |
| 132 | def popitem(self): |
| 133 | try: |
| 134 | key = next(iter(self)) |
| 135 | except StopIteration: |
| 136 | raise KeyError |
| 137 | value = self[key] |
| 138 | del self[key] |
| 139 | return key, value |
| 140 | |
| 141 | def clear(self): |
| 142 | try: |
| 143 | while True: |
| 144 | self.popitem() |
| 145 | except KeyError: |
| 146 | pass |
| 147 | |
| 148 | def update(*args, **kwds): |
| 149 | if len(args) > 2: |
| 150 | raise TypeError("update() takes at most 2 positional " |
| 151 | "arguments ({} given)".format(len(args))) |
| 152 | elif not args: |
| 153 | raise TypeError("update() takes at least 1 argument (0 given)") |
| 154 | self = args[0] |
| 155 | other = args[1] if len(args) >= 2 else () |
| 156 | |
| 157 | if isinstance(other, Mapping): |
| 158 | for key in other: |
| 159 | self[key] = other[key] |
| 160 | elif hasattr(other, "keys"): |
| 161 | for key in other.keys(): |
| 162 | self[key] = other[key] |
| 163 | else: |
| 164 | for key, value in other: |
| 165 | self[key] = value |
| 166 | for key, value in kwds.items(): |
| 167 | self[key] = value |
| 168 | |
| 169 | def setdefault(self, key, default=None): |
| 170 | try: |
| 171 | return self[key] |
| 172 | except KeyError: |
| 173 | self[key] = default |
| 174 | return default |
| 175 | |
| 176 | collections.Mapping.register(Mapping) |
| 177 | collections.MutableMapping.register(MutableMapping) |
| 178 | |
| 179 | else: |
| 180 | # In Python 3 we can just use MutableMapping directly, because it defines |
| 181 | # __slots__. |
| 182 | MutableMapping = collections.MutableMapping |
| 183 | |
| 184 | |
| 185 | class BaseContainer(object): |
| 186 | |
| 187 | """Base container class.""" |
| 188 | |
| 189 | # Minimizes memory usage and disallows assignment to other attributes. |
| 190 | __slots__ = ['_message_listener', '_values'] |
| 191 | |
| 192 | def __init__(self, message_listener): |
| 193 | """ |
| 194 | Args: |
| 195 | message_listener: A MessageListener implementation. |
| 196 | The RepeatedScalarFieldContainer will call this object's |
| 197 | Modified() method when it is modified. |
| 198 | """ |
| 199 | self._message_listener = message_listener |
| 200 | self._values = [] |
| 201 | |
| 202 | def __getitem__(self, key): |
| 203 | """Retrieves item by the specified key.""" |
| 204 | return self._values[key] |
| 205 | |
| 206 | def __len__(self): |
| 207 | """Returns the number of elements in the container.""" |
| 208 | return len(self._values) |
| 209 | |
| 210 | def __ne__(self, other): |
| 211 | """Checks if another instance isn't equal to this one.""" |
| 212 | # The concrete classes should define __eq__. |
| 213 | return not self == other |
| 214 | |
| 215 | def __hash__(self): |
| 216 | raise TypeError('unhashable object') |
| 217 | |
| 218 | def __repr__(self): |
| 219 | return repr(self._values) |
| 220 | |
| 221 | def sort(self, *args, **kwargs): |
| 222 | # Continue to support the old sort_function keyword argument. |
| 223 | # This is expected to be a rare occurrence, so use LBYL to avoid |
| 224 | # the overhead of actually catching KeyError. |
| 225 | if 'sort_function' in kwargs: |
| 226 | kwargs['cmp'] = kwargs.pop('sort_function') |
| 227 | self._values.sort(*args, **kwargs) |
| 228 | |
| 229 | |
| 230 | class RepeatedScalarFieldContainer(BaseContainer): |
| 231 | |
| 232 | """Simple, type-checked, list-like container for holding repeated scalars.""" |
| 233 | |
| 234 | # Disallows assignment to other attributes. |
| 235 | __slots__ = ['_type_checker'] |
| 236 | |
| 237 | def __init__(self, message_listener, type_checker): |
| 238 | """ |
| 239 | Args: |
| 240 | message_listener: A MessageListener implementation. |
| 241 | The RepeatedScalarFieldContainer will call this object's |
| 242 | Modified() method when it is modified. |
| 243 | type_checker: A type_checkers.ValueChecker instance to run on elements |
| 244 | inserted into this container. |
| 245 | """ |
| 246 | super(RepeatedScalarFieldContainer, self).__init__(message_listener) |
| 247 | self._type_checker = type_checker |
| 248 | |
| 249 | def append(self, value): |
| 250 | """Appends an item to the list. Similar to list.append().""" |
| 251 | self._values.append(self._type_checker.CheckValue(value)) |
| 252 | if not self._message_listener.dirty: |
| 253 | self._message_listener.Modified() |
| 254 | |
| 255 | def insert(self, key, value): |
| 256 | """Inserts the item at the specified position. Similar to list.insert().""" |
| 257 | self._values.insert(key, self._type_checker.CheckValue(value)) |
| 258 | if not self._message_listener.dirty: |
| 259 | self._message_listener.Modified() |
| 260 | |
| 261 | def extend(self, elem_seq): |
| 262 | """Extends by appending the given iterable. Similar to list.extend().""" |
| 263 | |
| 264 | if elem_seq is None: |
| 265 | return |
| 266 | try: |
| 267 | elem_seq_iter = iter(elem_seq) |
| 268 | except TypeError: |
| 269 | if not elem_seq: |
| 270 | # silently ignore falsy inputs :-/. |
| 271 | # TODO(ptucker): Deprecate this behavior. b/18413862 |
| 272 | return |
| 273 | raise |
| 274 | |
| 275 | new_values = [self._type_checker.CheckValue(elem) for elem in elem_seq_iter] |
| 276 | if new_values: |
| 277 | self._values.extend(new_values) |
| 278 | self._message_listener.Modified() |
| 279 | |
| 280 | def MergeFrom(self, other): |
| 281 | """Appends the contents of another repeated field of the same type to this |
| 282 | one. We do not check the types of the individual fields. |
| 283 | """ |
| 284 | self._values.extend(other._values) |
| 285 | self._message_listener.Modified() |
| 286 | |
| 287 | def remove(self, elem): |
| 288 | """Removes an item from the list. Similar to list.remove().""" |
| 289 | self._values.remove(elem) |
| 290 | self._message_listener.Modified() |
| 291 | |
| 292 | def pop(self, key=-1): |
| 293 | """Removes and returns an item at a given index. Similar to list.pop().""" |
| 294 | value = self._values[key] |
| 295 | self.__delitem__(key) |
| 296 | return value |
| 297 | |
| 298 | def __setitem__(self, key, value): |
| 299 | """Sets the item on the specified position.""" |
| 300 | if isinstance(key, slice): # PY3 |
| 301 | if key.step is not None: |
| 302 | raise ValueError('Extended slices not supported') |
| 303 | self.__setslice__(key.start, key.stop, value) |
| 304 | else: |
| 305 | self._values[key] = self._type_checker.CheckValue(value) |
| 306 | self._message_listener.Modified() |
| 307 | |
| 308 | def __getslice__(self, start, stop): |
| 309 | """Retrieves the subset of items from between the specified indices.""" |
| 310 | return self._values[start:stop] |
| 311 | |
| 312 | def __setslice__(self, start, stop, values): |
| 313 | """Sets the subset of items from between the specified indices.""" |
| 314 | new_values = [] |
| 315 | for value in values: |
| 316 | new_values.append(self._type_checker.CheckValue(value)) |
| 317 | self._values[start:stop] = new_values |
| 318 | self._message_listener.Modified() |
| 319 | |
| 320 | def __delitem__(self, key): |
| 321 | """Deletes the item at the specified position.""" |
| 322 | del self._values[key] |
| 323 | self._message_listener.Modified() |
| 324 | |
| 325 | def __delslice__(self, start, stop): |
| 326 | """Deletes the subset of items from between the specified indices.""" |
| 327 | del self._values[start:stop] |
| 328 | self._message_listener.Modified() |
| 329 | |
| 330 | def __eq__(self, other): |
| 331 | """Compares the current instance with another one.""" |
| 332 | if self is other: |
| 333 | return True |
| 334 | # Special case for the same type which should be common and fast. |
| 335 | if isinstance(other, self.__class__): |
| 336 | return other._values == self._values |
| 337 | # We are presumably comparing against some other sequence type. |
| 338 | return other == self._values |
| 339 | |
| 340 | collections.MutableSequence.register(BaseContainer) |
| 341 | |
| 342 | |
| 343 | class RepeatedCompositeFieldContainer(BaseContainer): |
| 344 | |
| 345 | """Simple, list-like container for holding repeated composite fields.""" |
| 346 | |
| 347 | # Disallows assignment to other attributes. |
| 348 | __slots__ = ['_message_descriptor'] |
| 349 | |
| 350 | def __init__(self, message_listener, message_descriptor): |
| 351 | """ |
| 352 | Note that we pass in a descriptor instead of the generated directly, |
| 353 | since at the time we construct a _RepeatedCompositeFieldContainer we |
| 354 | haven't yet necessarily initialized the type that will be contained in the |
| 355 | container. |
| 356 | |
| 357 | Args: |
| 358 | message_listener: A MessageListener implementation. |
| 359 | The RepeatedCompositeFieldContainer will call this object's |
| 360 | Modified() method when it is modified. |
| 361 | message_descriptor: A Descriptor instance describing the protocol type |
| 362 | that should be present in this container. We'll use the |
| 363 | _concrete_class field of this descriptor when the client calls add(). |
| 364 | """ |
| 365 | super(RepeatedCompositeFieldContainer, self).__init__(message_listener) |
| 366 | self._message_descriptor = message_descriptor |
| 367 | |
| 368 | def add(self, **kwargs): |
| 369 | """Adds a new element at the end of the list and returns it. Keyword |
| 370 | arguments may be used to initialize the element. |
| 371 | """ |
| 372 | new_element = self._message_descriptor._concrete_class(**kwargs) |
| 373 | new_element._SetListener(self._message_listener) |
| 374 | self._values.append(new_element) |
| 375 | if not self._message_listener.dirty: |
| 376 | self._message_listener.Modified() |
| 377 | return new_element |
| 378 | |
| 379 | def extend(self, elem_seq): |
| 380 | """Extends by appending the given sequence of elements of the same type |
| 381 | as this one, copying each individual message. |
| 382 | """ |
| 383 | message_class = self._message_descriptor._concrete_class |
| 384 | listener = self._message_listener |
| 385 | values = self._values |
| 386 | for message in elem_seq: |
| 387 | new_element = message_class() |
| 388 | new_element._SetListener(listener) |
| 389 | new_element.MergeFrom(message) |
| 390 | values.append(new_element) |
| 391 | listener.Modified() |
| 392 | |
| 393 | def MergeFrom(self, other): |
| 394 | """Appends the contents of another repeated field of the same type to this |
| 395 | one, copying each individual message. |
| 396 | """ |
| 397 | self.extend(other._values) |
| 398 | |
| 399 | def remove(self, elem): |
| 400 | """Removes an item from the list. Similar to list.remove().""" |
| 401 | self._values.remove(elem) |
| 402 | self._message_listener.Modified() |
| 403 | |
| 404 | def pop(self, key=-1): |
| 405 | """Removes and returns an item at a given index. Similar to list.pop().""" |
| 406 | value = self._values[key] |
| 407 | self.__delitem__(key) |
| 408 | return value |
| 409 | |
| 410 | def __getslice__(self, start, stop): |
| 411 | """Retrieves the subset of items from between the specified indices.""" |
| 412 | return self._values[start:stop] |
| 413 | |
| 414 | def __delitem__(self, key): |
| 415 | """Deletes the item at the specified position.""" |
| 416 | del self._values[key] |
| 417 | self._message_listener.Modified() |
| 418 | |
| 419 | def __delslice__(self, start, stop): |
| 420 | """Deletes the subset of items from between the specified indices.""" |
| 421 | del self._values[start:stop] |
| 422 | self._message_listener.Modified() |
| 423 | |
| 424 | def __eq__(self, other): |
| 425 | """Compares the current instance with another one.""" |
| 426 | if self is other: |
| 427 | return True |
| 428 | if not isinstance(other, self.__class__): |
| 429 | raise TypeError('Can only compare repeated composite fields against ' |
| 430 | 'other repeated composite fields.') |
| 431 | return self._values == other._values |
| 432 | |
| 433 | |
| 434 | class ScalarMap(MutableMapping): |
| 435 | |
| 436 | """Simple, type-checked, dict-like container for holding repeated scalars.""" |
| 437 | |
| 438 | # Disallows assignment to other attributes. |
| 439 | __slots__ = ['_key_checker', '_value_checker', '_values', '_message_listener'] |
| 440 | |
| 441 | def __init__(self, message_listener, key_checker, value_checker): |
| 442 | """ |
| 443 | Args: |
| 444 | message_listener: A MessageListener implementation. |
| 445 | The ScalarMap will call this object's Modified() method when it |
| 446 | is modified. |
| 447 | key_checker: A type_checkers.ValueChecker instance to run on keys |
| 448 | inserted into this container. |
| 449 | value_checker: A type_checkers.ValueChecker instance to run on values |
| 450 | inserted into this container. |
| 451 | """ |
| 452 | self._message_listener = message_listener |
| 453 | self._key_checker = key_checker |
| 454 | self._value_checker = value_checker |
| 455 | self._values = {} |
| 456 | |
| 457 | def __getitem__(self, key): |
| 458 | try: |
| 459 | return self._values[key] |
| 460 | except KeyError: |
| 461 | key = self._key_checker.CheckValue(key) |
| 462 | val = self._value_checker.DefaultValue() |
| 463 | self._values[key] = val |
| 464 | return val |
| 465 | |
| 466 | def __contains__(self, item): |
| 467 | # We check the key's type to match the strong-typing flavor of the API. |
| 468 | # Also this makes it easier to match the behavior of the C++ implementation. |
| 469 | self._key_checker.CheckValue(item) |
| 470 | return item in self._values |
| 471 | |
| 472 | # We need to override this explicitly, because our defaultdict-like behavior |
| 473 | # will make the default implementation (from our base class) always insert |
| 474 | # the key. |
| 475 | def get(self, key, default=None): |
| 476 | if key in self: |
| 477 | return self[key] |
| 478 | else: |
| 479 | return default |
| 480 | |
| 481 | def __setitem__(self, key, value): |
| 482 | checked_key = self._key_checker.CheckValue(key) |
| 483 | checked_value = self._value_checker.CheckValue(value) |
| 484 | self._values[checked_key] = checked_value |
| 485 | self._message_listener.Modified() |
| 486 | |
| 487 | def __delitem__(self, key): |
| 488 | del self._values[key] |
| 489 | self._message_listener.Modified() |
| 490 | |
| 491 | def __len__(self): |
| 492 | return len(self._values) |
| 493 | |
| 494 | def __iter__(self): |
| 495 | return iter(self._values) |
| 496 | |
| 497 | def __repr__(self): |
| 498 | return repr(self._values) |
| 499 | |
| 500 | def MergeFrom(self, other): |
| 501 | self._values.update(other._values) |
| 502 | self._message_listener.Modified() |
| 503 | |
| 504 | def InvalidateIterators(self): |
| 505 | # It appears that the only way to reliably invalidate iterators to |
| 506 | # self._values is to ensure that its size changes. |
| 507 | original = self._values |
| 508 | self._values = original.copy() |
| 509 | original[None] = None |
| 510 | |
| 511 | # This is defined in the abstract base, but we can do it much more cheaply. |
| 512 | def clear(self): |
| 513 | self._values.clear() |
| 514 | self._message_listener.Modified() |
| 515 | |
| 516 | |
| 517 | class MessageMap(MutableMapping): |
| 518 | |
| 519 | """Simple, type-checked, dict-like container for with submessage values.""" |
| 520 | |
| 521 | # Disallows assignment to other attributes. |
| 522 | __slots__ = ['_key_checker', '_values', '_message_listener', |
| 523 | '_message_descriptor'] |
| 524 | |
| 525 | def __init__(self, message_listener, message_descriptor, key_checker): |
| 526 | """ |
| 527 | Args: |
| 528 | message_listener: A MessageListener implementation. |
| 529 | The ScalarMap will call this object's Modified() method when it |
| 530 | is modified. |
| 531 | key_checker: A type_checkers.ValueChecker instance to run on keys |
| 532 | inserted into this container. |
| 533 | value_checker: A type_checkers.ValueChecker instance to run on values |
| 534 | inserted into this container. |
| 535 | """ |
| 536 | self._message_listener = message_listener |
| 537 | self._message_descriptor = message_descriptor |
| 538 | self._key_checker = key_checker |
| 539 | self._values = {} |
| 540 | |
| 541 | def __getitem__(self, key): |
| 542 | try: |
| 543 | return self._values[key] |
| 544 | except KeyError: |
| 545 | key = self._key_checker.CheckValue(key) |
| 546 | new_element = self._message_descriptor._concrete_class() |
| 547 | new_element._SetListener(self._message_listener) |
| 548 | self._values[key] = new_element |
| 549 | self._message_listener.Modified() |
| 550 | |
| 551 | return new_element |
| 552 | |
| 553 | def get_or_create(self, key): |
| 554 | """get_or_create() is an alias for getitem (ie. map[key]). |
| 555 | |
| 556 | Args: |
| 557 | key: The key to get or create in the map. |
| 558 | |
| 559 | This is useful in cases where you want to be explicit that the call is |
| 560 | mutating the map. This can avoid lint errors for statements like this |
| 561 | that otherwise would appear to be pointless statements: |
| 562 | |
| 563 | msg.my_map[key] |
| 564 | """ |
| 565 | return self[key] |
| 566 | |
| 567 | # We need to override this explicitly, because our defaultdict-like behavior |
| 568 | # will make the default implementation (from our base class) always insert |
| 569 | # the key. |
| 570 | def get(self, key, default=None): |
| 571 | if key in self: |
| 572 | return self[key] |
| 573 | else: |
| 574 | return default |
| 575 | |
| 576 | def __contains__(self, item): |
| 577 | return item in self._values |
| 578 | |
| 579 | def __setitem__(self, key, value): |
| 580 | raise ValueError('May not set values directly, call my_map[key].foo = 5') |
| 581 | |
| 582 | def __delitem__(self, key): |
| 583 | del self._values[key] |
| 584 | self._message_listener.Modified() |
| 585 | |
| 586 | def __len__(self): |
| 587 | return len(self._values) |
| 588 | |
| 589 | def __iter__(self): |
| 590 | return iter(self._values) |
| 591 | |
| 592 | def __repr__(self): |
| 593 | return repr(self._values) |
| 594 | |
| 595 | def MergeFrom(self, other): |
| 596 | for key in other: |
| 597 | self[key].MergeFrom(other[key]) |
| 598 | # self._message_listener.Modified() not required here, because |
| 599 | # mutations to submessages already propagate. |
| 600 | |
| 601 | def InvalidateIterators(self): |
| 602 | # It appears that the only way to reliably invalidate iterators to |
| 603 | # self._values is to ensure that its size changes. |
| 604 | original = self._values |
| 605 | self._values = original.copy() |
| 606 | original[None] = None |
| 607 | |
| 608 | # This is defined in the abstract base, but we can do it much more cheaply. |
| 609 | def clear(self): |
| 610 | self._values.clear() |
| 611 | self._message_listener.Modified() |