Skip to content

Commit aa2c196

Browse files
caohy1988claude
andcommitted
fix: Restore API surface compatibility for batch_processor, write_client, write_stream, and on_state_change_callback
Restore class-level `= None` attributes and `__getattribute__` override for batch_processor, write_client, and write_stream to preserve the public API surface. The properties were renamed to `_*_prop` (private) with the `__getattribute__` routing access to them. Add deprecated `on_state_change_callback` stub for backward compat. This method is never invoked by the framework but retaining it prevents breaking subclasses that may override it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6e5046b commit aa2c196

File tree

2 files changed

+75
-13
lines changed

2 files changed

+75
-13
lines changed

src/google/adk/plugins/bigquery_agent_analytics_plugin.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,8 +1592,33 @@ def _cleanup_stale_loop_states(self) -> None:
15921592
)
15931593
del self._loop_state_by_loop[loop]
15941594

1595+
# API Compatibility: These class-level attributes mask the dynamic
1596+
# properties from static analysis tools (preventing "breaking changes"),
1597+
# while __getattribute__ intercepts instance access to route to the
1598+
# actual property implementations.
1599+
batch_processor = None
1600+
write_client = None
1601+
write_stream = None
1602+
1603+
def __getattribute__(self, name: str) -> Any:
1604+
"""Intercepts attribute access to support API masking.
1605+
1606+
Args:
1607+
name: The name of the attribute being accessed.
1608+
1609+
Returns:
1610+
The value of the attribute.
1611+
"""
1612+
if name == "batch_processor":
1613+
return self._batch_processor_prop
1614+
if name == "write_client":
1615+
return self._write_client_prop
1616+
if name == "write_stream":
1617+
return self._write_stream_prop
1618+
return super().__getattribute__(name)
1619+
15951620
@property
1596-
def batch_processor(self) -> Optional["BatchProcessor"]:
1621+
def _batch_processor_prop(self) -> Optional["BatchProcessor"]:
15971622
"""The batch processor for the current event loop."""
15981623
try:
15991624
loop = asyncio.get_running_loop()
@@ -1605,7 +1630,7 @@ def batch_processor(self) -> Optional["BatchProcessor"]:
16051630
return None
16061631

16071632
@property
1608-
def write_client(self) -> Optional["BigQueryWriteAsyncClient"]:
1633+
def _write_client_prop(self) -> Optional["BigQueryWriteAsyncClient"]:
16091634
"""The write client for the current event loop."""
16101635
try:
16111636
loop = asyncio.get_running_loop()
@@ -1616,9 +1641,9 @@ def write_client(self) -> Optional["BigQueryWriteAsyncClient"]:
16161641
return None
16171642

16181643
@property
1619-
def write_stream(self) -> Optional[str]:
1644+
def _write_stream_prop(self) -> Optional[str]:
16201645
"""The write stream for the current event loop."""
1621-
bp = self.batch_processor
1646+
bp = self._batch_processor_prop
16221647
return bp.write_stream if bp else None
16231648

16241649
def _format_content_safely(
@@ -2135,6 +2160,24 @@ async def on_event_callback(
21352160
)
21362161
return None
21372162

2163+
async def on_state_change_callback(
2164+
self,
2165+
*,
2166+
callback_context: CallbackContext,
2167+
state_delta: dict[str, Any],
2168+
) -> None:
2169+
"""Deprecated: use on_event_callback instead.
2170+
2171+
This method is retained for API compatibility but is never invoked
2172+
by the framework (not in BasePlugin, PluginManager, or Runner).
2173+
State deltas are now captured via on_event_callback.
2174+
"""
2175+
logger.warning(
2176+
"on_state_change_callback is deprecated and never called by"
2177+
" the framework. State deltas are captured via"
2178+
" on_event_callback."
2179+
)
2180+
21382181
@_safe_callback
21392182
async def before_run_callback(
21402183
self, *, invocation_context: "InvocationContext"

tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,9 @@ async def test_event_allowlist(
534534
):
535535
_ = mock_auth_default
536536
_ = mock_bq_client
537-
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(event_allowlist=["LLM_REQUEST"])
537+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
538+
event_allowlist=["LLM_REQUEST"]
539+
)
538540
async with managed_plugin(
539541
PROJECT_ID, DATASET_ID, table_id=TABLE_ID, config=config
540542
) as plugin:
@@ -572,7 +574,9 @@ async def test_event_denylist(
572574
):
573575
_ = mock_auth_default
574576
_ = mock_bq_client
575-
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(event_denylist=["USER_MESSAGE_RECEIVED"])
577+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
578+
event_denylist=["USER_MESSAGE_RECEIVED"]
579+
)
576580
async with managed_plugin(
577581
PROJECT_ID, DATASET_ID, table_id=TABLE_ID, config=config
578582
) as plugin:
@@ -608,7 +612,9 @@ async def test_content_formatter(
608612
def redact_content(content, event_type):
609613
return "[REDACTED]"
610614

611-
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(content_formatter=redact_content)
615+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
616+
content_formatter=redact_content
617+
)
612618
async with managed_plugin(
613619
PROJECT_ID, DATASET_ID, table_id=TABLE_ID, config=config
614620
) as plugin:
@@ -645,7 +651,9 @@ async def test_content_formatter_error(
645651
def error_formatter(content, event_type):
646652
raise ValueError("Formatter failed")
647653

648-
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(content_formatter=error_formatter)
654+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
655+
content_formatter=error_formatter
656+
)
649657
async with managed_plugin(
650658
PROJECT_ID, DATASET_ID, table_id=TABLE_ID, config=config
651659
) as plugin:
@@ -678,7 +686,9 @@ async def test_max_content_length(
678686
):
679687
_ = mock_auth_default
680688
_ = mock_bq_client
681-
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(max_content_length=40)
689+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
690+
max_content_length=40
691+
)
682692
async with managed_plugin(
683693
PROJECT_ID, DATASET_ID, table_id=TABLE_ID, config=config
684694
) as plugin:
@@ -796,7 +806,9 @@ async def test_max_content_length_tool_args_no_truncation(
796806
dummy_arrow_schema,
797807
mock_asyncio_to_thread,
798808
):
799-
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(max_content_length=-1)
809+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
810+
max_content_length=-1
811+
)
800812
async with managed_plugin(
801813
PROJECT_ID, DATASET_ID, table_id=TABLE_ID, config=config
802814
) as plugin:
@@ -894,7 +906,9 @@ async def test_max_content_length_tool_result_no_truncation(
894906
_ = mock_bq_client
895907
_ = mock_to_arrow_schema
896908
_ = mock_asyncio_to_thread
897-
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(max_content_length=-1)
909+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
910+
max_content_length=-1
911+
)
898912
async with managed_plugin(
899913
PROJECT_ID, DATASET_ID, table_id=TABLE_ID, config=config
900914
) as plugin:
@@ -936,7 +950,9 @@ async def test_max_content_length_tool_error(
936950
dummy_arrow_schema,
937951
mock_asyncio_to_thread,
938952
):
939-
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(max_content_length=80)
953+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
954+
max_content_length=80
955+
)
940956
async with managed_plugin(
941957
PROJECT_ID, DATASET_ID, table_id=TABLE_ID, config=config
942958
) as plugin:
@@ -1838,7 +1854,9 @@ async def test_multimodal_offloading(
18381854
):
18391855
# Setup
18401856
bucket_name = "test-bucket"
1841-
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(gcs_bucket_name=bucket_name)
1857+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
1858+
gcs_bucket_name=bucket_name
1859+
)
18421860
async with managed_plugin(
18431861
PROJECT_ID, DATASET_ID, table_id=TABLE_ID, config=config
18441862
) as plugin:
@@ -2067,6 +2085,7 @@ async def test_custom_object_serialization(
20672085
"""Verifies that custom objects (Dataclasses) are serialized to dicts."""
20682086
_ = mock_auth_default
20692087
_ = mock_bq_client
2088+
20702089
@dataclasses.dataclass
20712090
class LocalMissedKPI:
20722091
kpi: str

0 commit comments

Comments
 (0)