From 5566c7ef0a96ab007e0fd811dc2b4b5769f00e6e Mon Sep 17 00:00:00 2001 From: Piyush Jagadish Bag Date: Tue, 21 Jul 2026 10:38:38 -0700 Subject: [PATCH] fix(utils): use protobuf-compatible repeated field detection Add _is_field_repeated helper that prefers field.is_repeated and falls back to field.label on older protobuf runtimes. Fixes crashes when newer protobuf builds remove label while the SDK still relied on it. Fixes #1011 --- src/a2a/utils/proto_utils.py | 23 +++++++++++------------ tests/utils/test_proto_utils.py | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index b191f98e0..299d7e9cf 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -174,10 +174,7 @@ def parse_params(params: QueryParams, message: ProtobufMessage) -> None: field = fields[k] v_list = params.getlist(k) - # TODO(https://github.com/a2aproject/a2a-python/issues/1011): Replace - # deprecated `field.label` with `field.is_repeated` once the minimum - # protobuf version requirement is bumped. - if field.label == FieldDescriptor.LABEL_REPEATED: + if _is_field_repeated(field): accumulated: list[Any] = [] for v in v_list: if not v: @@ -206,15 +203,20 @@ class ValidationDetail(TypedDict): message: str +def _is_field_repeated(field: FieldDescriptor) -> bool: + """Return whether a protobuf field is repeated across protobuf versions.""" + try: + return field.is_repeated + except AttributeError: + return field.label == FieldDescriptor.LABEL_REPEATED + + def _check_required_field_violation( msg: ProtobufMessage, field: FieldDescriptor ) -> ValidationDetail | None: """Check if a required field is missing or invalid.""" val = getattr(msg, field.name) - # TODO(https://github.com/a2aproject/a2a-python/issues/1011): Replace - # deprecated `field.label` with `field.is_repeated` once the minimum - # protobuf version requirement is bumped. - if field.label == FieldDescriptor.LABEL_REPEATED: + if _is_field_repeated(field): if not val: return ValidationDetail( field=field.name, @@ -255,10 +257,7 @@ def _recurse_validation( return errors val = getattr(msg, field.name) - # TODO(https://github.com/a2aproject/a2a-python/issues/1011): Replace - # deprecated `field.label` with `field.is_repeated` once the minimum - # protobuf version requirement is bumped. - if field.label != FieldDescriptor.LABEL_REPEATED: + if not _is_field_repeated(field): if msg.HasField(field.name): sub_errs = _validate_proto_required_fields_internal(val) _append_nested_errors(errors, field.name, sub_errs) diff --git a/tests/utils/test_proto_utils.py b/tests/utils/test_proto_utils.py index db49dbf05..0e61a224a 100644 --- a/tests/utils/test_proto_utils.py +++ b/tests/utils/test_proto_utils.py @@ -241,6 +241,31 @@ def _message_to_rest_params(self, message: ProtobufMessage) -> QueryParams: ).url.params +class TestIsFieldRepeated: + """Tests for _is_field_repeated helper.""" + + def test_scalar_field_not_repeated(self): + field = Message.DESCRIPTOR.fields_by_name['message_id'] + assert proto_utils._is_field_repeated(field) is False + + def test_repeated_field(self): + field = AgentSkill.DESCRIPTOR.fields_by_name['tags'] + assert proto_utils._is_field_repeated(field) is True + + def test_fallback_when_is_repeated_missing(self, monkeypatch): + field = AgentSkill.DESCRIPTOR.fields_by_name['tags'] + + def raise_attribute_error(_self): + raise AttributeError('is_repeated') + + monkeypatch.setattr( + type(field), + 'is_repeated', + property(raise_attribute_error), + ) + assert proto_utils._is_field_repeated(field) is True + + class TestValidateProtoRequiredFields: """Tests for validate_proto_required_fields function."""