Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix(utils): backward-compatible protobuf field.is_repeated (#1011)
  • Loading branch information
jacksjp committed May 5, 2026
commit 6cda9aea9b30877696f0fdc866dea3218b82d920
42 changes: 30 additions & 12 deletions src/a2a/utils/proto_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,43 @@
This module provides helper functions for common proto type operations.
"""

import logging
from typing import TYPE_CHECKING, Any, TypedDict

from google.api.field_behavior_pb2 import FieldBehavior, field_behavior
from google.protobuf import __version__ as protobuf_version
from google.protobuf.descriptor import FieldDescriptor
from google.protobuf.json_format import ParseDict
from google.protobuf.message import Message as ProtobufMessage
from google.rpc import error_details_pb2

from a2a.utils.errors import InvalidParamsError

Check failure on line 30 in src/a2a/utils/proto_utils.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

ruff (I001)

src/a2a/utils/proto_utils.py:20:1: I001 Import block is un-sorted or un-formatted help: Organize imports

logger = logging.getLogger(__name__)

# FieldDescriptor.is_repeated was introduced in protobuf 4.0; field.label was
# removed in protobuf 7.0. Check once at import time so _is_field_repeated()
# avoids a per-call hasattr probe on a hot path.
_PROTOBUF_HAS_IS_REPEATED: bool = hasattr(FieldDescriptor, 'is_repeated')

logger.debug(
'Protobuf %s: using %s API',
protobuf_version,
'field.is_repeated' if _PROTOBUF_HAS_IS_REPEATED else 'deprecated field.label',
)


def _is_field_repeated(field: FieldDescriptor) -> bool:
"""Return True if *field* is a repeated field.

Uses ``field.is_repeated`` (protobuf ≥ 4.0) when available, and falls back
to ``field.label == LABEL_REPEATED`` for older versions.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

The docstring mentions LABEL_REPEATED, but the implementation uses FieldDescriptor.LABEL_REPEATED. It's better to be consistent with the code to avoid confusion, especially since LABEL_REPEATED is not imported into the local namespace. Accessing descriptor constants via the FieldDescriptor class is the preferred idiomatic approach in this repository.

Suggested change
to ``field.label == LABEL_REPEATED`` for older versions.
to field.label == FieldDescriptor.LABEL_REPEATED for older versions.
References
  1. When checking for repeated fields in Protobuf, access descriptor constants via the FieldDescriptor class (e.g., FieldDescriptor.LABEL_REPEATED) rather than directly from the field instance.

See https://github.com/a2aproject/a2a-python/issues/1011.
"""
if _PROTOBUF_HAS_IS_REPEATED:
return field.is_repeated # type: ignore[attr-defined]
return field.label == FieldDescriptor.LABEL_REPEATED # type: ignore[attr-defined]


if TYPE_CHECKING:
from starlette.datastructures import QueryParams
Expand All @@ -36,13 +63,13 @@
except ImportError:
QueryParams = Any

from a2a.types.a2a_pb2 import (
Message,
StreamResponse,
Task,
TaskArtifactUpdateEvent,
TaskStatusUpdateEvent,
)

Check failure on line 72 in src/a2a/utils/proto_utils.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

ruff (E402)

src/a2a/utils/proto_utils.py:66:1: E402 Module level import not at top of file


# Define Event type locally to avoid circular imports
Expand Down Expand Up @@ -174,10 +201,7 @@
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:
Expand Down Expand Up @@ -211,10 +235,7 @@
) -> 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,
Expand Down Expand Up @@ -255,10 +276,7 @@
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)
Expand Down
Loading