Skip to content

feat: add proto2pydantic Pydantic model generation pipeline#910

Open
brucearctor wants to merge 3 commits intoa2aproject:1.0-devfrom
brucearctor:feat/pydantic-codegen
Open

feat: add proto2pydantic Pydantic model generation pipeline#910
brucearctor wants to merge 3 commits intoa2aproject:1.0-devfrom
brucearctor:feat/pydantic-codegen

Conversation

@brucearctor
Copy link

Summary

Adds protoc-gen-proto2pydantic to the code generation pipeline, producing Pydantic models alongside existing _pb2.py files. This is a purely additive change — no existing code is modified.

What's New

  • buf.gen.yaml: Added protoc-gen-proto2pydantic plugin with base_class=a2a._base.A2ABaseModel and preset=a2a
  • scripts/gen_proto.sh: Added go install step for proto2pydantic@v0.4.0
  • src/a2a/types/a2a_pydantic.py: Generated 39 Pydantic models + 2 enums from a2a.proto

Generated Model Features

  • All models extend A2ABaseModel (inherits ConfigDict with to_camel_custom alias generator)
  • to_proto_json() method on every model for ProtoJSON-compatible serialization
  • Python keyword escaping: StringList.list_ with alias='list'
  • @field_serializer for Timestamp fields (RFC 3339 format)
  • Oneof unions: Part.content, SecurityScheme.scheme, OAuthFlows.flow
  • __all__ exports for all generated types

Usage

# Existing protobuf API (unchanged)
from a2a.types import AgentCard, Message, Task

# New Pydantic API (opt-in)
from a2a.types.a2a_pydantic import AgentCard, Message, Task

Test Results

All existing tests pass with zero changes:

1132 passed, 101 skipped, 3 xfailed

Related

Add protoc-gen-proto2pydantic plugin to buf.gen.yaml for generating
Pydantic models alongside existing protobuf _pb2 objects.

Generated models:
- 39 Pydantic models + 2 enums from a2a.proto
- All extend A2ABaseModel with to_proto_json() for ProtoJSON compat
- Keyword escaping (list -> list_ with alias='list')
- Timestamp field serializers for RFC 3339
- Oneof unions for Part.content, SecurityScheme.scheme, etc.

This is an additive change - existing a2a_pb2 imports are untouched.
New Pydantic models available via: from a2a.types.a2a_pydantic import ...
@github-actions
Copy link

github-actions bot commented Mar 26, 2026

🧪 Code Coverage (vs 1.0-dev)

⬇️ Download Full Report

Base PR Delta
src/a2a/types/a2a_pydantic.py (new) 0.00%
Total 91.55% 87.84% 🔴 -3.71%

Generated by coverage-comment.yml

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request integrates Pydantic model generation into the Protobuf workflow. It adds the protoc-gen-proto2pydantic plugin to the Buf configuration, updates the generation script to install the plugin, and introduces the generated models in src/a2a/types/a2a_pydantic.py. The review feedback identifies opportunities to strengthen type hints by replacing Any with a specific Protobuf message base class and suggests a more readable approach for millisecond formatting in timestamp serialization.

metadata: dict[str, Any] = Field(default=None, description='Optional. metadata associated with this part.')
filename: str = Field(default='', description='An optional `filename` for the file (e.g., "document.pdf").')
media_type: str = Field(default='', description='The `media_type` (MIME type) of the part content (e.g., "text/plain", "application/json", "image/png"). This field is available for all part types.')
content: str | bytes | str | Any | None = None
Copy link
Contributor

Choose a reason for hiding this comment

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

low

The type hint for content contains a redundant str and uses Any. To maintain stronger type checking, prefer using the common base class google.protobuf.message.Message over typing.Any for Protobuf message types as per repository rules.

Suggested change
content: str | bytes | str | Any | None = None
content: str | bytes | google.protobuf.message.Message | None = None
References
  1. For type hints involving multiple Protobuf message types, prefer using the common base class google.protobuf.message.Message over typing.Any to maintain stronger type checking.

"""Serialize datetime to RFC 3339 with UTC 'Z' suffix for ProtoJSON."""
if v is None:
return None
return v.strftime('%Y-%m-%dT%H:%M:%S.') + f'{v.microsecond:06d}'[:3] + 'Z'
Copy link
Contributor

Choose a reason for hiding this comment

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

low

The logic to extract milliseconds (f'{v.microsecond:06d}'[:3]) is a bit indirect. Using integer division (//) would be more explicit and arguably more readable for getting the millisecond part of the timestamp.

This pattern is also repeated in ListTasksRequest._serialize_timestamp.

Suggested change
return v.strftime('%Y-%m-%dT%H:%M:%S.') + f'{v.microsecond:06d}'[:3] + 'Z'
return v.strftime('%Y-%m-%dT%H:%M:%S.') + f'{v.microsecond // 1000:03d}' + 'Z'

Generator improvements applied:
- Single quotes in __all__ entries
- Alphabetically sorted __all__
- Docstring trailing periods (PEP 257)
- Deduplicated union types in oneof fields
- Smart description quoting (double quotes when containing apostrophes)
- isort-compliant import ordering (stdlib, third-party, local)
- TYPE_CHECKING for datetime import with model_rebuild()
- TYPE_CHECKING before Any in typing imports
- gen_proto.sh: bump proto2pydantic v0.4.0 -> v0.5.0
- a2a_pydantic.py: improved millisecond formatting in timestamp serializer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant