Skip to content

Commit 5bebfd4

Browse files
committed
fix: Event.message honors subclass field
_accept_convenience_kwargs already routes construction kwargs to a subclass-declared `message` field, but the property/setter always used `content`, so reads of that field returned the content alias instead of the stored value. Defer to the field when present; base Event behavior (message aliases content) is unchanged. Change-Id: I8dd492407b8117ad9c6a90fb24269a03dac49ac0
1 parent bc3a4fa commit 5bebfd4

2 files changed

Lines changed: 60 additions & 4 deletions

File tree

src/google/adk/events/event.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,30 @@ def _accept_convenience_kwargs(cls, data: Any) -> Any:
230230
return data
231231

232232
@property
233-
def message(self) -> Optional[types.Content]:
234-
"""Alias for content. Returns the user-facing message of the event."""
233+
def message(self) -> Any:
234+
"""Alias for content. Returns the user-facing message of the event.
235+
236+
Subclasses may declare ``message`` as a real field (see
237+
``_accept_convenience_kwargs``, which already routes construction kwargs to
238+
such fields). When they do, return that field's value so reads stay
239+
consistent with construction and serialization instead of returning the
240+
``content`` alias. The return type is ``Any`` because such a subclass field
241+
may be typed differently (e.g. ``str``); for a plain ``Event`` this returns
242+
``Optional[types.Content]``.
243+
"""
244+
if 'message' in type(self).model_fields:
245+
return self.__dict__.get('message')
235246
return self.content
236247

237248
@message.setter
238-
def message(self, value: Optional[types.ContentUnion]) -> None:
239-
"""Sets the content of the event."""
249+
def message(self, value: Any) -> None:
250+
"""Sets the content of the event (or a subclass ``message`` field)."""
251+
if 'message' in type(self).model_fields:
252+
# Route through Pydantic so a subclass field's validators/type are
253+
# enforced. A direct __dict__ write would skip validation, and
254+
# object.__setattr__/self.message would recurse through this property.
255+
self.__pydantic_validator__.validate_assignment(self, 'message', value)
256+
return
240257
if value is not None:
241258
from google.genai import _transformers
242259

tests/unittests/events/test_event_message.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,42 @@ def test_message_with_route(self):
137137
event = Event(message='hello', route='next')
138138
assert event.content is not None
139139
assert event.actions.route == 'next'
140+
141+
142+
class TestMessageSubclassField:
143+
"""Tests that a subclass declaring `message` as a real field is honored.
144+
145+
`_accept_convenience_kwargs` already routes construction kwargs to such a
146+
field; the `message` property/setter must defer to it too instead of
147+
aliasing `content`.
148+
"""
149+
150+
def test_subclass_field_readable_via_property(self):
151+
class _Sub(Event):
152+
message: str = ''
153+
154+
event = _Sub(message='hello', author='a')
155+
assert event.message == 'hello'
156+
157+
def test_subclass_field_serializes_and_round_trips(self):
158+
class _Sub(Event):
159+
message: str = ''
160+
161+
event = _Sub(message='hello', author='a')
162+
data = event.model_dump()
163+
assert data['message'] == 'hello'
164+
assert _Sub.model_validate(data).message == 'hello'
165+
166+
def test_subclass_field_setter_updates_field_not_content(self):
167+
class _Sub(Event):
168+
message: str = ''
169+
170+
event = _Sub(message='hello', author='a')
171+
event.message = 'updated'
172+
assert event.message == 'updated'
173+
assert event.content is None
174+
175+
def test_base_event_message_still_aliases_content(self):
176+
content = types.Content(parts=[types.Part(text='hi')], role='model')
177+
event = Event(content=content)
178+
assert event.message is event.content

0 commit comments

Comments
 (0)