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 helper functions used to create protocol message classes from 32Descriptor objects at runtime backed by the protocol buffer C++ API. 33""" 34 35__author__ = 'petar@google.com (Petar Petrov)' 36 37import copy_reg 38import operator 39from google.protobuf.internal import _net_proto2___python 40from google.protobuf.internal import enum_type_wrapper 41from google.protobuf import message 42 43 44_LABEL_REPEATED = _net_proto2___python.LABEL_REPEATED 45_LABEL_OPTIONAL = _net_proto2___python.LABEL_OPTIONAL 46_CPPTYPE_MESSAGE = _net_proto2___python.CPPTYPE_MESSAGE 47_TYPE_MESSAGE = _net_proto2___python.TYPE_MESSAGE 48 49 50def GetDescriptorPool(): 51 """Creates a new DescriptorPool C++ object.""" 52 return _net_proto2___python.NewCDescriptorPool() 53 54 55_pool = GetDescriptorPool() 56 57 58def GetFieldDescriptor(full_field_name): 59 """Searches for a field descriptor given a full field name.""" 60 return _pool.FindFieldByName(full_field_name) 61 62 63def BuildFile(content): 64 """Registers a new proto file in the underlying C++ descriptor pool.""" 65 _net_proto2___python.BuildFile(content) 66 67 68def GetExtensionDescriptor(full_extension_name): 69 """Searches for extension descriptor given a full field name.""" 70 return _pool.FindExtensionByName(full_extension_name) 71 72 73def NewCMessage(full_message_name): 74 """Creates a new C++ protocol message by its name.""" 75 return _net_proto2___python.NewCMessage(full_message_name) 76 77 78def ScalarProperty(cdescriptor): 79 """Returns a scalar property for the given descriptor.""" 80 81 def Getter(self): 82 return self._cmsg.GetScalar(cdescriptor) 83 84 def Setter(self, value): 85 self._cmsg.SetScalar(cdescriptor, value) 86 87 return property(Getter, Setter) 88 89 90def CompositeProperty(cdescriptor, message_type): 91 """Returns a Python property the given composite field.""" 92 93 def Getter(self): 94 sub_message = self._composite_fields.get(cdescriptor.name, None) 95 if sub_message is None: 96 cmessage = self._cmsg.NewSubMessage(cdescriptor) 97 sub_message = message_type._concrete_class(__cmessage=cmessage) 98 self._composite_fields[cdescriptor.name] = sub_message 99 return sub_message 100 101 return property(Getter) 102 103 104class RepeatedScalarContainer(object): 105 """Container for repeated scalar fields.""" 106 107 __slots__ = ['_message', '_cfield_descriptor', '_cmsg'] 108 109 def __init__(self, msg, cfield_descriptor): 110 self._message = msg 111 self._cmsg = msg._cmsg 112 self._cfield_descriptor = cfield_descriptor 113 114 def append(self, value): 115 self._cmsg.AddRepeatedScalar( 116 self._cfield_descriptor, value) 117 118 def extend(self, sequence): 119 for element in sequence: 120 self.append(element) 121 122 def insert(self, key, value): 123 values = self[slice(None, None, None)] 124 values.insert(key, value) 125 self._cmsg.AssignRepeatedScalar(self._cfield_descriptor, values) 126 127 def remove(self, value): 128 values = self[slice(None, None, None)] 129 values.remove(value) 130 self._cmsg.AssignRepeatedScalar(self._cfield_descriptor, values) 131 132 def __setitem__(self, key, value): 133 values = self[slice(None, None, None)] 134 values[key] = value 135 self._cmsg.AssignRepeatedScalar(self._cfield_descriptor, values) 136 137 def __getitem__(self, key): 138 return self._cmsg.GetRepeatedScalar(self._cfield_descriptor, key) 139 140 def __delitem__(self, key): 141 self._cmsg.DeleteRepeatedField(self._cfield_descriptor, key) 142 143 def __len__(self): 144 return len(self[slice(None, None, None)]) 145 146 def __eq__(self, other): 147 if self is other: 148 return True 149 if not operator.isSequenceType(other): 150 raise TypeError( 151 'Can only compare repeated scalar fields against sequences.') 152 # We are presumably comparing against some other sequence type. 153 return other == self[slice(None, None, None)] 154 155 def __ne__(self, other): 156 return not self == other 157 158 def __hash__(self): 159 raise TypeError('unhashable object') 160 161 def sort(self, *args, **kwargs): 162 # Maintain compatibility with the previous interface. 163 if 'sort_function' in kwargs: 164 kwargs['cmp'] = kwargs.pop('sort_function') 165 self._cmsg.AssignRepeatedScalar(self._cfield_descriptor, 166 sorted(self, *args, **kwargs)) 167 168 169def RepeatedScalarProperty(cdescriptor): 170 """Returns a Python property the given repeated scalar field.""" 171 172 def Getter(self): 173 container = self._composite_fields.get(cdescriptor.name, None) 174 if container is None: 175 container = RepeatedScalarContainer(self, cdescriptor) 176 self._composite_fields[cdescriptor.name] = container 177 return container 178 179 def Setter(self, new_value): 180 raise AttributeError('Assignment not allowed to repeated field ' 181 '"%s" in protocol message object.' % cdescriptor.name) 182 183 doc = 'Magic attribute generated for "%s" proto field.' % cdescriptor.name 184 return property(Getter, Setter, doc=doc) 185 186 187class RepeatedCompositeContainer(object): 188 """Container for repeated composite fields.""" 189 190 __slots__ = ['_message', '_subclass', '_cfield_descriptor', '_cmsg'] 191 192 def __init__(self, msg, cfield_descriptor, subclass): 193 self._message = msg 194 self._cmsg = msg._cmsg 195 self._subclass = subclass 196 self._cfield_descriptor = cfield_descriptor 197 198 def add(self, **kwargs): 199 cmessage = self._cmsg.AddMessage(self._cfield_descriptor) 200 return self._subclass(__cmessage=cmessage, __owner=self._message, **kwargs) 201 202 def extend(self, elem_seq): 203 """Extends by appending the given sequence of elements of the same type 204 as this one, copying each individual message. 205 """ 206 for message in elem_seq: 207 self.add().MergeFrom(message) 208 209 def remove(self, value): 210 # TODO(protocol-devel): This is inefficient as it needs to generate a 211 # message pointer for each message only to do index(). Move this to a C++ 212 # extension function. 213 self.__delitem__(self[slice(None, None, None)].index(value)) 214 215 def MergeFrom(self, other): 216 for message in other[:]: 217 self.add().MergeFrom(message) 218 219 def __getitem__(self, key): 220 cmessages = self._cmsg.GetRepeatedMessage( 221 self._cfield_descriptor, key) 222 subclass = self._subclass 223 if not isinstance(cmessages, list): 224 return subclass(__cmessage=cmessages, __owner=self._message) 225 226 return [subclass(__cmessage=m, __owner=self._message) for m in cmessages] 227 228 def __delitem__(self, key): 229 self._cmsg.DeleteRepeatedField( 230 self._cfield_descriptor, key) 231 232 def __len__(self): 233 return self._cmsg.FieldLength(self._cfield_descriptor) 234 235 def __eq__(self, other): 236 """Compares the current instance with another one.""" 237 if self is other: 238 return True 239 if not isinstance(other, self.__class__): 240 raise TypeError('Can only compare repeated composite fields against ' 241 'other repeated composite fields.') 242 messages = self[slice(None, None, None)] 243 other_messages = other[slice(None, None, None)] 244 return messages == other_messages 245 246 def __hash__(self): 247 raise TypeError('unhashable object') 248 249 def sort(self, cmp=None, key=None, reverse=False, **kwargs): 250 # Maintain compatibility with the old interface. 251 if cmp is None and 'sort_function' in kwargs: 252 cmp = kwargs.pop('sort_function') 253 254 # The cmp function, if provided, is passed the results of the key function, 255 # so we only need to wrap one of them. 256 if key is None: 257 index_key = self.__getitem__ 258 else: 259 index_key = lambda i: key(self[i]) 260 261 # Sort the list of current indexes by the underlying object. 262 indexes = range(len(self)) 263 indexes.sort(cmp=cmp, key=index_key, reverse=reverse) 264 265 # Apply the transposition. 266 for dest, src in enumerate(indexes): 267 if dest == src: 268 continue 269 self._cmsg.SwapRepeatedFieldElements(self._cfield_descriptor, dest, src) 270 # Don't swap the same value twice. 271 indexes[src] = src 272 273 274def RepeatedCompositeProperty(cdescriptor, message_type): 275 """Returns a Python property for the given repeated composite field.""" 276 277 def Getter(self): 278 container = self._composite_fields.get(cdescriptor.name, None) 279 if container is None: 280 container = RepeatedCompositeContainer( 281 self, cdescriptor, message_type._concrete_class) 282 self._composite_fields[cdescriptor.name] = container 283 return container 284 285 def Setter(self, new_value): 286 raise AttributeError('Assignment not allowed to repeated field ' 287 '"%s" in protocol message object.' % cdescriptor.name) 288 289 doc = 'Magic attribute generated for "%s" proto field.' % cdescriptor.name 290 return property(Getter, Setter, doc=doc) 291 292 293class ExtensionDict(object): 294 """Extension dictionary added to each protocol message.""" 295 296 def __init__(self, msg): 297 self._message = msg 298 self._cmsg = msg._cmsg 299 self._values = {} 300 301 def __setitem__(self, extension, value): 302 from google.protobuf import descriptor 303 if not isinstance(extension, descriptor.FieldDescriptor): 304 raise KeyError('Bad extension %r.' % (extension,)) 305 cdescriptor = extension._cdescriptor 306 if (cdescriptor.label != _LABEL_OPTIONAL or 307 cdescriptor.cpp_type == _CPPTYPE_MESSAGE): 308 raise TypeError('Extension %r is repeated and/or a composite type.' % ( 309 extension.full_name,)) 310 self._cmsg.SetScalar(cdescriptor, value) 311 self._values[extension] = value 312 313 def __getitem__(self, extension): 314 from google.protobuf import descriptor 315 if not isinstance(extension, descriptor.FieldDescriptor): 316 raise KeyError('Bad extension %r.' % (extension,)) 317 318 cdescriptor = extension._cdescriptor 319 if (cdescriptor.label != _LABEL_REPEATED and 320 cdescriptor.cpp_type != _CPPTYPE_MESSAGE): 321 return self._cmsg.GetScalar(cdescriptor) 322 323 ext = self._values.get(extension, None) 324 if ext is not None: 325 return ext 326 327 ext = self._CreateNewHandle(extension) 328 self._values[extension] = ext 329 return ext 330 331 def ClearExtension(self, extension): 332 from google.protobuf import descriptor 333 if not isinstance(extension, descriptor.FieldDescriptor): 334 raise KeyError('Bad extension %r.' % (extension,)) 335 self._cmsg.ClearFieldByDescriptor(extension._cdescriptor) 336 if extension in self._values: 337 del self._values[extension] 338 339 def HasExtension(self, extension): 340 from google.protobuf import descriptor 341 if not isinstance(extension, descriptor.FieldDescriptor): 342 raise KeyError('Bad extension %r.' % (extension,)) 343 return self._cmsg.HasFieldByDescriptor(extension._cdescriptor) 344 345 def _FindExtensionByName(self, name): 346 """Tries to find a known extension with the specified name. 347 348 Args: 349 name: Extension full name. 350 351 Returns: 352 Extension field descriptor. 353 """ 354 return self._message._extensions_by_name.get(name, None) 355 356 def _CreateNewHandle(self, extension): 357 cdescriptor = extension._cdescriptor 358 if (cdescriptor.label != _LABEL_REPEATED and 359 cdescriptor.cpp_type == _CPPTYPE_MESSAGE): 360 cmessage = self._cmsg.NewSubMessage(cdescriptor) 361 return extension.message_type._concrete_class(__cmessage=cmessage) 362 363 if cdescriptor.label == _LABEL_REPEATED: 364 if cdescriptor.cpp_type == _CPPTYPE_MESSAGE: 365 return RepeatedCompositeContainer( 366 self._message, cdescriptor, extension.message_type._concrete_class) 367 else: 368 return RepeatedScalarContainer(self._message, cdescriptor) 369 # This shouldn't happen! 370 assert False 371 return None 372 373 374def NewMessage(bases, message_descriptor, dictionary): 375 """Creates a new protocol message *class*.""" 376 _AddClassAttributesForNestedExtensions(message_descriptor, dictionary) 377 _AddEnumValues(message_descriptor, dictionary) 378 _AddDescriptors(message_descriptor, dictionary) 379 return bases 380 381 382def InitMessage(message_descriptor, cls): 383 """Constructs a new message instance (called before instance's __init__).""" 384 cls._extensions_by_name = {} 385 _AddInitMethod(message_descriptor, cls) 386 _AddMessageMethods(message_descriptor, cls) 387 _AddPropertiesForExtensions(message_descriptor, cls) 388 copy_reg.pickle(cls, lambda obj: (cls, (), obj.__getstate__())) 389 390 391def _AddDescriptors(message_descriptor, dictionary): 392 """Sets up a new protocol message class dictionary. 393 394 Args: 395 message_descriptor: A Descriptor instance describing this message type. 396 dictionary: Class dictionary to which we'll add a '__slots__' entry. 397 """ 398 dictionary['__descriptors'] = {} 399 for field in message_descriptor.fields: 400 dictionary['__descriptors'][field.name] = GetFieldDescriptor( 401 field.full_name) 402 403 dictionary['__slots__'] = list(dictionary['__descriptors'].iterkeys()) + [ 404 '_cmsg', '_owner', '_composite_fields', 'Extensions', '_HACK_REFCOUNTS'] 405 406 407def _AddEnumValues(message_descriptor, dictionary): 408 """Sets class-level attributes for all enum fields defined in this message. 409 410 Args: 411 message_descriptor: Descriptor object for this message type. 412 dictionary: Class dictionary that should be populated. 413 """ 414 for enum_type in message_descriptor.enum_types: 415 dictionary[enum_type.name] = enum_type_wrapper.EnumTypeWrapper(enum_type) 416 for enum_value in enum_type.values: 417 dictionary[enum_value.name] = enum_value.number 418 419 420def _AddClassAttributesForNestedExtensions(message_descriptor, dictionary): 421 """Adds class attributes for the nested extensions.""" 422 extension_dict = message_descriptor.extensions_by_name 423 for extension_name, extension_field in extension_dict.iteritems(): 424 assert extension_name not in dictionary 425 dictionary[extension_name] = extension_field 426 427 428def _AddInitMethod(message_descriptor, cls): 429 """Adds an __init__ method to cls.""" 430 431 # Create and attach message field properties to the message class. 432 # This can be done just once per message class, since property setters and 433 # getters are passed the message instance. 434 # This makes message instantiation extremely fast, and at the same time it 435 # doesn't require the creation of property objects for each message instance, 436 # which saves a lot of memory. 437 for field in message_descriptor.fields: 438 field_cdescriptor = cls.__descriptors[field.name] 439 if field.label == _LABEL_REPEATED: 440 if field.cpp_type == _CPPTYPE_MESSAGE: 441 value = RepeatedCompositeProperty(field_cdescriptor, field.message_type) 442 else: 443 value = RepeatedScalarProperty(field_cdescriptor) 444 elif field.cpp_type == _CPPTYPE_MESSAGE: 445 value = CompositeProperty(field_cdescriptor, field.message_type) 446 else: 447 value = ScalarProperty(field_cdescriptor) 448 setattr(cls, field.name, value) 449 450 # Attach a constant with the field number. 451 constant_name = field.name.upper() + '_FIELD_NUMBER' 452 setattr(cls, constant_name, field.number) 453 454 def Init(self, **kwargs): 455 """Message constructor.""" 456 cmessage = kwargs.pop('__cmessage', None) 457 if cmessage: 458 self._cmsg = cmessage 459 else: 460 self._cmsg = NewCMessage(message_descriptor.full_name) 461 462 # Keep a reference to the owner, as the owner keeps a reference to the 463 # underlying protocol buffer message. 464 owner = kwargs.pop('__owner', None) 465 if owner: 466 self._owner = owner 467 468 if message_descriptor.is_extendable: 469 self.Extensions = ExtensionDict(self) 470 else: 471 # Reference counting in the C++ code is broken and depends on 472 # the Extensions reference to keep this object alive during unit 473 # tests (see b/4856052). Remove this once b/4945904 is fixed. 474 self._HACK_REFCOUNTS = self 475 self._composite_fields = {} 476 477 for field_name, field_value in kwargs.iteritems(): 478 field_cdescriptor = self.__descriptors.get(field_name, None) 479 if not field_cdescriptor: 480 raise ValueError('Protocol message has no "%s" field.' % field_name) 481 if field_cdescriptor.label == _LABEL_REPEATED: 482 if field_cdescriptor.cpp_type == _CPPTYPE_MESSAGE: 483 field_name = getattr(self, field_name) 484 for val in field_value: 485 field_name.add().MergeFrom(val) 486 else: 487 getattr(self, field_name).extend(field_value) 488 elif field_cdescriptor.cpp_type == _CPPTYPE_MESSAGE: 489 getattr(self, field_name).MergeFrom(field_value) 490 else: 491 setattr(self, field_name, field_value) 492 493 Init.__module__ = None 494 Init.__doc__ = None 495 cls.__init__ = Init 496 497 498def _IsMessageSetExtension(field): 499 """Checks if a field is a message set extension.""" 500 return (field.is_extension and 501 field.containing_type.has_options and 502 field.containing_type.GetOptions().message_set_wire_format and 503 field.type == _TYPE_MESSAGE and 504 field.message_type == field.extension_scope and 505 field.label == _LABEL_OPTIONAL) 506 507 508def _AddMessageMethods(message_descriptor, cls): 509 """Adds the methods to a protocol message class.""" 510 if message_descriptor.is_extendable: 511 512 def ClearExtension(self, extension): 513 self.Extensions.ClearExtension(extension) 514 515 def HasExtension(self, extension): 516 return self.Extensions.HasExtension(extension) 517 518 def HasField(self, field_name): 519 return self._cmsg.HasField(field_name) 520 521 def ClearField(self, field_name): 522 child_cmessage = None 523 if field_name in self._composite_fields: 524 child_field = self._composite_fields[field_name] 525 del self._composite_fields[field_name] 526 527 child_cdescriptor = self.__descriptors[field_name] 528 # TODO(anuraag): Support clearing repeated message fields as well. 529 if (child_cdescriptor.label != _LABEL_REPEATED and 530 child_cdescriptor.cpp_type == _CPPTYPE_MESSAGE): 531 child_field._owner = None 532 child_cmessage = child_field._cmsg 533 534 if child_cmessage is not None: 535 self._cmsg.ClearField(field_name, child_cmessage) 536 else: 537 self._cmsg.ClearField(field_name) 538 539 def Clear(self): 540 cmessages_to_release = [] 541 for field_name, child_field in self._composite_fields.iteritems(): 542 child_cdescriptor = self.__descriptors[field_name] 543 # TODO(anuraag): Support clearing repeated message fields as well. 544 if (child_cdescriptor.label != _LABEL_REPEATED and 545 child_cdescriptor.cpp_type == _CPPTYPE_MESSAGE): 546 child_field._owner = None 547 cmessages_to_release.append((child_cdescriptor, child_field._cmsg)) 548 self._composite_fields.clear() 549 self._cmsg.Clear(cmessages_to_release) 550 551 def IsInitialized(self, errors=None): 552 if self._cmsg.IsInitialized(): 553 return True 554 if errors is not None: 555 errors.extend(self.FindInitializationErrors()); 556 return False 557 558 def SerializeToString(self): 559 if not self.IsInitialized(): 560 raise message.EncodeError( 561 'Message %s is missing required fields: %s' % ( 562 self._cmsg.full_name, ','.join(self.FindInitializationErrors()))) 563 return self._cmsg.SerializeToString() 564 565 def SerializePartialToString(self): 566 return self._cmsg.SerializePartialToString() 567 568 def ParseFromString(self, serialized): 569 self.Clear() 570 self.MergeFromString(serialized) 571 572 def MergeFromString(self, serialized): 573 byte_size = self._cmsg.MergeFromString(serialized) 574 if byte_size < 0: 575 raise message.DecodeError('Unable to merge from string.') 576 return byte_size 577 578 def MergeFrom(self, msg): 579 if not isinstance(msg, cls): 580 raise TypeError( 581 "Parameter to MergeFrom() must be instance of same class: " 582 "expected %s got %s." % (cls.__name__, type(msg).__name__)) 583 self._cmsg.MergeFrom(msg._cmsg) 584 585 def CopyFrom(self, msg): 586 self._cmsg.CopyFrom(msg._cmsg) 587 588 def ByteSize(self): 589 return self._cmsg.ByteSize() 590 591 def SetInParent(self): 592 return self._cmsg.SetInParent() 593 594 def ListFields(self): 595 all_fields = [] 596 field_list = self._cmsg.ListFields() 597 fields_by_name = cls.DESCRIPTOR.fields_by_name 598 for is_extension, field_name in field_list: 599 if is_extension: 600 extension = cls._extensions_by_name[field_name] 601 all_fields.append((extension, self.Extensions[extension])) 602 else: 603 field_descriptor = fields_by_name[field_name] 604 all_fields.append( 605 (field_descriptor, getattr(self, field_name))) 606 all_fields.sort(key=lambda item: item[0].number) 607 return all_fields 608 609 def FindInitializationErrors(self): 610 return self._cmsg.FindInitializationErrors() 611 612 def __str__(self): 613 return str(self._cmsg) 614 615 def __eq__(self, other): 616 if self is other: 617 return True 618 if not isinstance(other, self.__class__): 619 return False 620 return self.ListFields() == other.ListFields() 621 622 def __ne__(self, other): 623 return not self == other 624 625 def __hash__(self): 626 raise TypeError('unhashable object') 627 628 def __unicode__(self): 629 # Lazy import to prevent circular import when text_format imports this file. 630 from google.protobuf import text_format 631 return text_format.MessageToString(self, as_utf8=True).decode('utf-8') 632 633 # Attach the local methods to the message class. 634 for key, value in locals().copy().iteritems(): 635 if key not in ('key', 'value', '__builtins__', '__name__', '__doc__'): 636 setattr(cls, key, value) 637 638 # Static methods: 639 640 def RegisterExtension(extension_handle): 641 extension_handle.containing_type = cls.DESCRIPTOR 642 cls._extensions_by_name[extension_handle.full_name] = extension_handle 643 644 if _IsMessageSetExtension(extension_handle): 645 # MessageSet extension. Also register under type name. 646 cls._extensions_by_name[ 647 extension_handle.message_type.full_name] = extension_handle 648 cls.RegisterExtension = staticmethod(RegisterExtension) 649 650 def FromString(string): 651 msg = cls() 652 msg.MergeFromString(string) 653 return msg 654 cls.FromString = staticmethod(FromString) 655 656 657 658def _AddPropertiesForExtensions(message_descriptor, cls): 659 """Adds properties for all fields in this protocol message type.""" 660 extension_dict = message_descriptor.extensions_by_name 661 for extension_name, extension_field in extension_dict.iteritems(): 662 constant_name = extension_name.upper() + '_FIELD_NUMBER' 663 setattr(cls, constant_name, extension_field.number) 664