From 1803f8bf28a0b61be8869d333bfd70171861e673 Mon Sep 17 00:00:00 2001 From: Matej Aleksandrov Date: Fri, 20 Dec 2024 03:31:05 -0800 Subject: [PATCH] Ensure protobuf Message.__dir__ returns proto fields Update Message.__dir__ method to ensure all proto fields of a message are included in the list of attributes. This solves the inconsistency between the Fast C++ and UPB proto implementation. The Fast C++ implementation was always returning fields while UPB never. Improve a unit test to detect that. PiperOrigin-RevId: 708262631 --- .../google/protobuf/internal/message_test.py | 8 ++++-- python/google/protobuf/message.py | 25 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/python/google/protobuf/internal/message_test.py b/python/google/protobuf/internal/message_test.py index 9981604230b22..244631112d5ef 100755 --- a/python/google/protobuf/internal/message_test.py +++ b/python/google/protobuf/internal/message_test.py @@ -1278,11 +1278,15 @@ def testReturningType(self, message_module): def testDir(self, message_module): m = message_module.TestAllTypes() attributes = dir(m) - self.assertGreaterEqual(len(attributes), 55) + self.assertGreaterEqual(len(attributes), 124) + + attribute_set = set(attributes) self.assertIn('DESCRIPTOR', attributes) + self.assertIn('oneof_string', attribute_set) + self.assertIn('optional_double', attribute_set) + self.assertIn('repeated_float', attribute_set) class_attributes = dir(type(m)) - attribute_set = set(attributes) for attr in class_attributes: if attr != 'Extensions': self.assertIn(attr, attribute_set) diff --git a/python/google/protobuf/message.py b/python/google/protobuf/message.py index 0eed00eed0c49..d48f4f409d055 100755 --- a/python/google/protobuf/message.py +++ b/python/google/protobuf/message.py @@ -60,18 +60,27 @@ def __deepcopy__(self, memo=None): return clone def __dir__(self): - """Filters out attributes that would raise AttributeError if accessed.""" - missing_attributes = set() + """Provides the list of all accessible Message attributes.""" + message_attributes = set(super().__dir__()) + + # TODO: Remove this once the UPB implementation is improved. + # The UPB proto implementation currently doesn't provide proto fields as + # attributes and they have to added. + if self.DESCRIPTOR is not None: + for field in self.DESCRIPTOR.fields: + message_attributes.add(field.name) + + # The Fast C++ proto implementation provides inaccessible attributes that + # have to be removed. for attribute in _INCONSISTENT_MESSAGE_ATTRIBUTES: + if attribute not in message_attributes: + continue try: getattr(self, attribute) except AttributeError: - missing_attributes.add(attribute) - return [ - attribute - for attribute in super().__dir__() - if attribute not in missing_attributes - ] + message_attributes.remove(attribute) + + return sorted(message_attributes) def __eq__(self, other_msg): """Recursively compares two messages by value and structure."""