Skip to content

Commit f22b384

Browse files
authored
fix: Add deprecation warning for camelCase alias (#334)
1 parent 0bb5563 commit f22b384

File tree

4 files changed

+55
-15
lines changed

4 files changed

+55
-15
lines changed

src/a2a/_base.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
13
from typing import Any, ClassVar
24

35
from pydantic import BaseModel, ConfigDict
@@ -27,7 +29,8 @@ class A2ABaseModel(BaseModel):
2729
serves as the foundation for future extensions or shared utilities.
2830
2931
This implementation provides backward compatibility for camelCase aliases
30-
by lazy-loading an alias map upon first use.
32+
by lazy-loading an alias map upon first use. Accessing or setting
33+
attributes via their camelCase alias will raise a DeprecationWarning.
3134
"""
3235

3336
model_config = ConfigDict(
@@ -60,14 +63,37 @@ def __setattr__(self, name: str, value: Any) -> None:
6063
"""Allow setting attributes via their camelCase alias."""
6164
# Get the map and find the corresponding snake_case field name.
6265
field_name = type(self)._get_alias_map().get(name) # noqa: SLF001
66+
67+
if field_name:
68+
# An alias was used, issue a warning.
69+
warnings.warn(
70+
(
71+
f"Setting field '{name}' via its camelCase alias is deprecated and will be removed in version 0.3.0 "
72+
f"Use the snake_case name '{field_name}' instead."
73+
),
74+
DeprecationWarning,
75+
stacklevel=2,
76+
)
77+
6378
# If an alias was used, field_name will be set; otherwise, use the original name.
6479
super().__setattr__(field_name or name, value)
6580

6681
def __getattr__(self, name: str) -> Any:
6782
"""Allow getting attributes via their camelCase alias."""
6883
# Get the map and find the corresponding snake_case field name.
6984
field_name = type(self)._get_alias_map().get(name) # noqa: SLF001
85+
7086
if field_name:
87+
# An alias was used, issue a warning.
88+
warnings.warn(
89+
(
90+
f"Accessing field '{name}' via its camelCase alias is deprecated and will be removed in version 0.3.0 "
91+
f"Use the snake_case name '{field_name}' instead."
92+
),
93+
DeprecationWarning,
94+
stacklevel=2,
95+
)
96+
7197
# If an alias was used, retrieve the actual snake_case attribute.
7298
return getattr(self, field_name)
7399

src/a2a/server/request_handlers/default_request_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,10 @@ async def _setup_message_execution(
200200
)
201201

202202
task = task_manager.update_with_message(params.message, task)
203-
elif params.message.taskId:
203+
elif params.message.task_id:
204204
raise ServerError(
205205
error=TaskNotFoundError(
206-
message=f'Task {params.message.taskId} was specified but does not exist'
206+
message=f'Task {params.message.task_id} was specified but does not exist'
207207
)
208208
)
209209

tests/server/request_handlers/test_default_request_handler.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,7 +1787,7 @@ async def test_on_resubscribe_to_task_in_terminal_state(terminal_state):
17871787

17881788
@pytest.mark.asyncio
17891789
async def test_on_message_send_task_id_provided_but_task_not_found():
1790-
"""Test on_message_send when taskId is provided but task doesn't exist."""
1790+
"""Test on_message_send when task_id is provided but task doesn't exist."""
17911791
task_id = 'nonexistent_task'
17921792
mock_task_store = AsyncMock(spec=TaskStore)
17931793

@@ -1798,10 +1798,10 @@ async def test_on_message_send_task_id_provided_but_task_not_found():
17981798
params = MessageSendParams(
17991799
message=Message(
18001800
role=Role.user,
1801-
messageId='msg_nonexistent',
1801+
message_id='msg_nonexistent',
18021802
parts=[Part(root=TextPart(text='Hello'))],
1803-
taskId=task_id,
1804-
contextId='ctx1',
1803+
task_id=task_id,
1804+
context_id='ctx1',
18051805
)
18061806
)
18071807

@@ -1827,7 +1827,7 @@ async def test_on_message_send_task_id_provided_but_task_not_found():
18271827

18281828
@pytest.mark.asyncio
18291829
async def test_on_message_send_stream_task_id_provided_but_task_not_found():
1830-
"""Test on_message_send_stream when taskId is provided but task doesn't exist."""
1830+
"""Test on_message_send_stream when task_id is provided but task doesn't exist."""
18311831
task_id = 'nonexistent_stream_task'
18321832
mock_task_store = AsyncMock(spec=TaskStore)
18331833

@@ -1838,10 +1838,10 @@ async def test_on_message_send_stream_task_id_provided_but_task_not_found():
18381838
params = MessageSendParams(
18391839
message=Message(
18401840
role=Role.user,
1841-
messageId='msg_nonexistent_stream',
1841+
message_id='msg_nonexistent_stream',
18421842
parts=[Part(root=TextPart(text='Hello'))],
1843-
taskId=task_id,
1844-
contextId='ctx1',
1843+
task_id=task_id,
1844+
context_id='ctx1',
18451845
)
18461846
)
18471847

tests/test_types.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,8 +1551,22 @@ def test_camelCase() -> None:
15511551
supportsAuthenticatedExtendedCard=True,
15521552
)
15531553

1554-
agent_card.supportsAuthenticatedExtendedCard = False
1555-
1556-
default_input_modes = agent_card.defaultInputModes
1557-
assert agent_card
1554+
# Test setting an attribute via camelCase alias
1555+
# We expect a DeprecationWarning with a specific message
1556+
with pytest.warns(
1557+
DeprecationWarning,
1558+
match="Setting field 'supportsAuthenticatedExtendedCard'",
1559+
):
1560+
agent_card.supportsAuthenticatedExtendedCard = False
1561+
1562+
# Test getting an attribute via camelCase alias
1563+
# We expect another DeprecationWarning with a specific message
1564+
with pytest.warns(
1565+
DeprecationWarning, match="Accessing field 'defaultInputModes'"
1566+
):
1567+
default_input_modes = agent_card.defaultInputModes
1568+
1569+
# Assert the functionality still works as expected
1570+
assert agent_card.supports_authenticated_extended_card is False
15581571
assert default_input_modes == ['text']
1572+
assert agent_card.default_input_modes == ['text']

0 commit comments

Comments
 (0)