Skip to content

Commit cd94167

Browse files
authored
refactor!: Remove custom __getattr__ and __setattr__ for camelCase fields in types.py (#335)
Release-As: 0.3.0
1 parent a96e5af commit cd94167

File tree

3 files changed

+33
-75
lines changed

3 files changed

+33
-75
lines changed

src/a2a/_base.py

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import warnings
2-
3-
from typing import Any, ClassVar
4-
51
from pydantic import BaseModel, ConfigDict
62
from pydantic.alias_generators import to_camel
73

@@ -40,64 +36,3 @@ class A2ABaseModel(BaseModel):
4036
serialize_by_alias=True,
4137
alias_generator=to_camel_custom,
4238
)
43-
44-
# Cache for the alias -> field_name mapping.
45-
# It starts as None and is populated on first access.
46-
_alias_to_field_name_map: ClassVar[dict[str, str] | None] = None
47-
48-
@classmethod
49-
def _get_alias_map(cls) -> dict[str, str]:
50-
"""Lazily builds and returns the alias-to-field-name mapping for the class.
51-
52-
The map is cached on the class object to avoid re-computation.
53-
"""
54-
if cls._alias_to_field_name_map is None:
55-
cls._alias_to_field_name_map = {
56-
field.alias: field_name
57-
for field_name, field in cls.model_fields.items()
58-
if field.alias is not None
59-
}
60-
return cls._alias_to_field_name_map
61-
62-
def __setattr__(self, name: str, value: Any) -> None:
63-
"""Allow setting attributes via their camelCase alias."""
64-
# Get the map and find the corresponding snake_case field name.
65-
field_name = type(self)._get_alias_map().get(name) # noqa: SLF001
66-
67-
if field_name and field_name != 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-
78-
# If an alias was used, field_name will be set; otherwise, use the original name.
79-
super().__setattr__(field_name or name, value)
80-
81-
def __getattr__(self, name: str) -> Any:
82-
"""Allow getting attributes via their camelCase alias."""
83-
# Get the map and find the corresponding snake_case field name.
84-
field_name = type(self)._get_alias_map().get(name) # noqa: SLF001
85-
86-
if field_name and field_name != 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-
97-
# If an alias was used, retrieve the actual snake_case attribute.
98-
return getattr(self, field_name)
99-
100-
# If it's not a known alias, it's a genuine missing attribute.
101-
raise AttributeError(
102-
f"'{type(self).__name__}' object has no attribute '{name}'"
103-
)

src/a2a/utils/proto_utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,12 @@ def security_scheme(
363363
flows=cls.oauth2_flows(scheme.root.flows),
364364
)
365365
)
366+
if isinstance(scheme.root, types.MutualTLSSecurityScheme):
367+
return a2a_pb2.SecurityScheme(
368+
mtls_security_scheme=a2a_pb2.MutualTlsSecurityScheme(
369+
description=scheme.root.description,
370+
)
371+
)
366372
return a2a_pb2.SecurityScheme(
367373
open_id_connect_security_scheme=a2a_pb2.OpenIdConnectSecurityScheme(
368374
description=scheme.root.description,

tests/test_types.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,7 +1556,11 @@ def test_use_get_task_push_notification_params_for_request() -> None:
15561556
)
15571557

15581558

1559-
def test_camelCase() -> None:
1559+
def test_camelCase_access_raises_attribute_error() -> None:
1560+
"""
1561+
Tests that accessing or setting fields via their camelCase alias
1562+
raises an AttributeError.
1563+
"""
15601564
skill = AgentSkill(
15611565
id='hello_world',
15621566
name='Returns hello world',
@@ -1565,6 +1569,7 @@ def test_camelCase() -> None:
15651569
examples=['hi', 'hello world'],
15661570
)
15671571

1572+
# Initialization with camelCase still works due to Pydantic's populate_by_name config
15681573
agent_card = AgentCard(
15691574
name='Hello World Agent',
15701575
description='Just a hello world agent',
@@ -1577,21 +1582,33 @@ def test_camelCase() -> None:
15771582
supportsAuthenticatedExtendedCard=True, # type: ignore
15781583
)
15791584

1580-
# Test setting an attribute via camelCase alias
1581-
with pytest.warns(
1582-
DeprecationWarning,
1583-
match="Setting field 'supportsAuthenticatedExtendedCard'",
1585+
# --- Test that using camelCase aliases raises errors ---
1586+
1587+
# Test setting an attribute via camelCase alias raises AttributeError
1588+
with pytest.raises(
1589+
ValueError,
1590+
match='"AgentCard" object has no field "supportsAuthenticatedExtendedCard"',
15841591
):
15851592
agent_card.supportsAuthenticatedExtendedCard = False
15861593

1587-
# Test getting an attribute via camelCase alias
1588-
with pytest.warns(
1589-
DeprecationWarning, match="Accessing field 'defaultInputModes'"
1594+
# Test getting an attribute via camelCase alias raises AttributeError
1595+
with pytest.raises(
1596+
AttributeError,
1597+
match="'AgentCard' object has no attribute 'defaultInputModes'",
15901598
):
1591-
default_input_modes = agent_card.defaultInputModes
1599+
_ = agent_card.defaultInputModes
15921600

1593-
# Assert the functionality still works as expected
1601+
# --- Test that using snake_case names works correctly ---
1602+
1603+
# The value should be unchanged because the camelCase setattr failed
1604+
assert agent_card.supports_authenticated_extended_card is True
1605+
1606+
# Now, set it correctly using the snake_case name
1607+
agent_card.supports_authenticated_extended_card = False
15941608
assert agent_card.supports_authenticated_extended_card is False
1609+
1610+
# Get the attribute correctly using the snake_case name
1611+
default_input_modes = agent_card.default_input_modes
15951612
assert default_input_modes == ['text']
15961613
assert agent_card.default_input_modes == ['text']
15971614

0 commit comments

Comments
 (0)