Skip to content

Commit

Permalink
Ensure protobuf Message.__dir__ returns proto fields
Browse files Browse the repository at this point in the history
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
  • Loading branch information
AleksMat authored and copybara-github committed Dec 20, 2024
1 parent 3b2bb51 commit 1803f8b
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 10 deletions.
8 changes: 6 additions & 2 deletions python/google/protobuf/internal/message_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 17 additions & 8 deletions python/google/protobuf/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down

0 comments on commit 1803f8b

Please sign in to comment.