From 7f1a4adb41c582f3608b221b1ca74a72bd61d6f4 Mon Sep 17 00:00:00 2001 From: Joshua Samuel Date: Mon, 22 Dec 2025 18:28:28 +1100 Subject: [PATCH] fix: emit deprecation warning only when deprecated aliases are accessed Previously, the deprecation warning was emitted at module import time, which triggered whenever `strands` was imported because other modules import from `experimental.hooks`. Changed to use `__getattr__` to lazily emit the warning only when the deprecated aliases (BeforeToolInvocationEvent, AfterToolInvocationEvent, BeforeModelInvocationEvent, AfterModelInvocationEvent) are actually accessed. Fixes #1236 --- src/strands/experimental/hooks/__init__.py | 17 +++++++--- src/strands/experimental/hooks/events.py | 32 ++++++++++++------- .../experimental/hooks/test_hook_aliases.py | 18 ++++++----- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/strands/experimental/hooks/__init__.py b/src/strands/experimental/hooks/__init__.py index c76b57ea4..d2bec33a0 100644 --- a/src/strands/experimental/hooks/__init__.py +++ b/src/strands/experimental/hooks/__init__.py @@ -1,19 +1,26 @@ """Experimental hook functionality that has not yet reached stability.""" from .events import ( - AfterModelInvocationEvent, - AfterToolInvocationEvent, - BeforeModelInvocationEvent, - BeforeToolInvocationEvent, + BidiAfterConnectionRestartEvent, BidiAfterInvocationEvent, BidiAfterToolCallEvent, BidiAgentInitializedEvent, + BidiBeforeConnectionRestartEvent, BidiBeforeInvocationEvent, BidiBeforeToolCallEvent, BidiInterruptionEvent, BidiMessageAddedEvent, ) +# Deprecated aliases are accessed via __getattr__ to emit warnings only on use + + +def __getattr__(name: str): + from . import events + + return getattr(events, name) + + __all__ = [ "BeforeToolInvocationEvent", "AfterToolInvocationEvent", @@ -27,4 +34,6 @@ "BidiBeforeToolCallEvent", "BidiAfterToolCallEvent", "BidiInterruptionEvent", + "BidiBeforeConnectionRestartEvent", + "BidiAfterConnectionRestartEvent", ] diff --git a/src/strands/experimental/hooks/events.py b/src/strands/experimental/hooks/events.py index 8a8d80629..081190af3 100644 --- a/src/strands/experimental/hooks/events.py +++ b/src/strands/experimental/hooks/events.py @@ -5,7 +5,7 @@ import warnings from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Literal, TypeAlias +from typing import TYPE_CHECKING, Any, Literal from ...hooks.events import AfterModelCallEvent, AfterToolCallEvent, BeforeModelCallEvent, BeforeToolCallEvent from ...hooks.registry import BaseHookEvent @@ -16,17 +16,25 @@ from ..bidi.agent.agent import BidiAgent from ..bidi.models import BidiModelTimeoutError -warnings.warn( - "BeforeModelCallEvent, AfterModelCallEvent, BeforeToolCallEvent, and AfterToolCallEvent are no longer experimental." - "Import from strands.hooks instead.", - DeprecationWarning, - stacklevel=2, -) - -BeforeToolInvocationEvent: TypeAlias = BeforeToolCallEvent -AfterToolInvocationEvent: TypeAlias = AfterToolCallEvent -BeforeModelInvocationEvent: TypeAlias = BeforeModelCallEvent -AfterModelInvocationEvent: TypeAlias = AfterModelCallEvent +# Deprecated aliases - warning emitted on access via __getattr__ +_DEPRECATED_ALIASES = { + "BeforeToolInvocationEvent": BeforeToolCallEvent, + "AfterToolInvocationEvent": AfterToolCallEvent, + "BeforeModelInvocationEvent": BeforeModelCallEvent, + "AfterModelInvocationEvent": AfterModelCallEvent, +} + + +def __getattr__(name: str) -> Any: + if name in _DEPRECATED_ALIASES: + warnings.warn( + f"{name} has been moved to production with an updated name. " + f"Use {_DEPRECATED_ALIASES[name].__name__} from strands.hooks instead.", + DeprecationWarning, + stacklevel=2, + ) + return _DEPRECATED_ALIASES[name] + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") # BidiAgent Hook Events diff --git a/tests/strands/experimental/hooks/test_hook_aliases.py b/tests/strands/experimental/hooks/test_hook_aliases.py index f4899f2ab..2da8a6f90 100644 --- a/tests/strands/experimental/hooks/test_hook_aliases.py +++ b/tests/strands/experimental/hooks/test_hook_aliases.py @@ -112,18 +112,20 @@ def experimental_callback(event: BeforeToolInvocationEvent): assert received_event is test_event -def test_deprecation_warning_on_import(captured_warnings): - """Verify that importing from experimental module emits deprecation warning.""" +def test_deprecation_warning_on_access(captured_warnings): + """Verify that accessing deprecated aliases emits deprecation warning.""" + import strands.experimental.hooks.events as events_module - module = sys.modules.get("strands.experimental.hooks.events") - if module: - importlib.reload(module) - else: - importlib.import_module("strands.experimental.hooks.events") + # Clear any existing warnings + captured_warnings.clear() + + # Access a deprecated alias - this should trigger the warning + _ = events_module.BeforeToolInvocationEvent assert len(captured_warnings) == 1 assert issubclass(captured_warnings[0].category, DeprecationWarning) - assert "are no longer experimental" in str(captured_warnings[0].message) + assert "BeforeToolInvocationEvent" in str(captured_warnings[0].message) + assert "BeforeToolCallEvent" in str(captured_warnings[0].message) def test_deprecation_warning_on_import_only_for_experimental(captured_warnings):