Skip to content

feat: Support snake_case for all fields in in types.py #199

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ AServer
AServers
AService
AStarlette
AUser
EUR
GBP
GVsb
INR
JPY
JSONRPCt
Expand All @@ -29,6 +31,7 @@ coro
datamodel
dunders
euo
excinfo
genai
getkwargs
gle
Expand All @@ -39,9 +42,11 @@ lifecycles
linting
lstrips
mockurl
notif
oauthoidc
oidc
opensource
otherurl
protoc
pyi
pyversions
Expand Down
5 changes: 0 additions & 5 deletions .github/actions/spelling/expect.txt

This file was deleted.

27 changes: 26 additions & 1 deletion .github/workflows/update-a2a-types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ on:
repository_dispatch:
types: [a2a_json_update]
workflow_dispatch:
pull_request:
branches:
- main
paths:
- "scripts/generate_types.sh"
- "src/a2a/pydantic_base.py"
types:
- opened
- synchronize
- reopened

jobs:
generate_and_pr:
Expand All @@ -15,6 +25,9 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}

- name: Set up Python
uses: actions/setup-python@v5
Expand Down Expand Up @@ -53,7 +66,19 @@ jobs:
uv run scripts/grpc_gen_post_processor.py
echo "Buf generate finished."

- name: Create Pull Request with Updates
- name: Commit changes to current PR
if: github.event_name == 'pull_request' # Only run this step for pull_request events
run: |
git config user.name "a2a-bot"
git config user.email "[email protected]"
git add ${{ steps.vars.outputs.GENERATED_FILE }} src/a2a/grpc/
git diff --cached --exit-code || git commit -m "feat: Update A2A types from specification 🤖"
git push
env:
GITHUB_TOKEN: ${{ secrets.A2A_BOT_PAT }}

- name: Create Pull Request with Updates (for repository_dispatch/workflow_dispatch)
if: github.event_name != 'pull_request'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.A2A_BOT_PAT }}
Expand Down
6 changes: 5 additions & 1 deletion scripts/generate_types.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ uv run datamodel-codegen \
--output "$GENERATED_FILE" \
--target-python-version 3.10 \
--output-model-type pydantic_v2.BaseModel \
--base-class a2a.pydantic_base.A2ABaseModel \
--disable-timestamp \
--use-schema-description \
--use-union-operator \
Expand All @@ -32,6 +33,9 @@ uv run datamodel-codegen \
--use-one-literal-as-default \
--class-name A2A \
--use-standard-collections \
--use-subclass-enum
--use-subclass-enum \
--snake-case-field \
--no-alias


echo "Codegen finished successfully."
4 changes: 3 additions & 1 deletion src/a2a/client/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ def create_text_message_object(
A `Message` object with a new UUID messageId.
"""
return Message(
role=role, parts=[Part(TextPart(text=content))], messageId=str(uuid4())
role=role,
parts=[Part(root=TextPart(text=content))],
message_id=str(uuid4()),
)
23 changes: 23 additions & 0 deletions src/a2a/pydantic_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""A2A Pydantic Base Model with shared configuration."""

from typing import Any

from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel, to_snake


class A2ABaseModel(BaseModel):
"""Base model for all A2A types with shared configuration."""

model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True,
)

def __getattr__(self, name: str) -> Any: # noqa: D105
snake = to_snake(name)
if hasattr(self, snake):
return getattr(self, snake)
raise AttributeError(
f'{type(self).__name__} object has no attribute {name!r}'
)
16 changes: 8 additions & 8 deletions src/a2a/server/tasks/task_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ async def save_task_event(
when the TaskManager's ID is already set.
"""
task_id_from_event = (
event.id if isinstance(event, Task) else event.taskId
event.id if isinstance(event, Task) else event.task_id
)
# If task id is known, make sure it is matched
if self.task_id and self.task_id != task_id_from_event:
Expand All @@ -107,8 +107,8 @@ async def save_task_event(
)
if not self.task_id:
self.task_id = task_id_from_event
if not self.context_id and self.context_id != event.contextId:
self.context_id = event.contextId
if not self.context_id and self.context_id != event.context_id:
self.context_id = event.context_id

logger.debug(
'Processing save of task event of type %s for task_id: %s',
Expand Down Expand Up @@ -160,12 +160,12 @@ async def ensure_task(
if not task:
logger.info(
'Task not found or task_id not set. Creating new task for event (task_id: %s, context_id: %s).',
event.taskId,
event.contextId,
event.task_id,
event.context_id,
)
# streaming agent did not previously stream task object.
# Create a task object with the available information and persist the event
task = self._init_task_obj(event.taskId, event.contextId)
task = self._init_task_obj(event.task_id, event.context_id)
await self._save_task(task)

return task
Expand Down Expand Up @@ -207,7 +207,7 @@ def _init_task_obj(self, task_id: str, context_id: str) -> Task:
history = [self._initial_message] if self._initial_message else []
return Task(
id=task_id,
contextId=context_id,
context_id=context_id,
status=TaskStatus(state=TaskState.submitted),
history=history,
)
Expand All @@ -224,7 +224,7 @@ async def _save_task(self, task: Task) -> None:
if not self.task_id:
logger.info('New task created with id: %s', task.id)
self.task_id = task.id
self.context_id = task.contextId
self.context_id = task.context_id

def update_with_message(self, message: Message, task: Task) -> Task:
"""Updates a task object in memory by adding a new message to its history.
Expand Down
Loading
Loading