feat: add proto2pydantic Pydantic model generation pipeline#910
feat: add proto2pydantic Pydantic model generation pipeline#910brucearctor wants to merge 7 commits intoa2aproject:1.0-devfrom
Conversation
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 ...
🧪 Code Coverage (vs
|
| Base | PR | Delta | |
|---|---|---|---|
| src/a2a/types/a2a_pydantic.py (new) | — | 0.00% | — |
| Total | 91.55% | 87.84% | 🔴 -3.71% |
Generated by coverage-comment.yml
There was a problem hiding this comment.
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.
src/a2a/types/a2a_pydantic.py
Outdated
| 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 |
There was a problem hiding this comment.
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.
| content: str | bytes | str | Any | None = None | |
| content: str | bytes | google.protobuf.message.Message | None = None |
References
- 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.
src/a2a/types/a2a_pydantic.py
Outdated
| """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' |
There was a problem hiding this comment.
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.
| 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
- Add 'protocgen' to spelling allow.txt (proto2pydantic plugin org) - Exclude generated *_pydantic.py from jscpd copy/paste detection
- Enum fields default to zero value (e.g., TaskState.TASK_STATE_UNSPECIFIED) - Message fields properly annotated as | None - Fixes all mypy and pyright type errors in generated code
- Add 'ruff format' to gen_proto.sh after buf generate - Apply formatting to a2a_pydantic.py (line-length wrapping for long Field() descriptions, consistent blank lines)
|
Related conversation: #884 |
Summary
Adds
protoc-gen-proto2pydanticto the code generation pipeline, producing Pydantic models alongside existing_pb2.pyfiles. This is a purely additive change — no existing code is modified.What's New
buf.gen.yaml: Addedprotoc-gen-proto2pydanticplugin withbase_class=a2a._base.A2ABaseModelandpreset=a2ascripts/gen_proto.sh: Addedgo installstep forproto2pydantic@v0.4.0src/a2a/types/a2a_pydantic.py: Generated 39 Pydantic models + 2 enums froma2a.protoGenerated Model Features
A2ABaseModel(inheritsConfigDictwithto_camel_customalias generator)to_proto_json()method on every model for ProtoJSON-compatible serializationStringList.list_withalias='list'@field_serializerfor Timestamp fields (RFC 3339 format)Part.content,SecurityScheme.scheme,OAuthFlows.flow__all__exports for all generated typesUsage
Test Results
All existing tests pass with zero changes:
Related