From c8efb70bf779c0b9deed7e246f7dbe007f2ef4de Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Thu, 8 Aug 2019 18:36:43 -0700 Subject: [PATCH 1/5] AutoSaveMiddleware added --- .../botbuilder/core/__init__.py | 4 + .../core/auto_save_state_middleware.py | 29 +++++ .../botbuilder/core/bot_state_set.py | 29 +++++ .../tests/test_auto_save_middleware.py | 113 ++++++++++++++++++ .../dialogs/prompts/number_prompt.py | 2 +- 5 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 libraries/botbuilder-core/botbuilder/core/auto_save_state_middleware.py create mode 100644 libraries/botbuilder-core/botbuilder/core/bot_state_set.py create mode 100644 libraries/botbuilder-core/tests/test_auto_save_middleware.py diff --git a/libraries/botbuilder-core/botbuilder/core/__init__.py b/libraries/botbuilder-core/botbuilder/core/__init__.py index d596d2d07..f34eec933 100644 --- a/libraries/botbuilder-core/botbuilder/core/__init__.py +++ b/libraries/botbuilder-core/botbuilder/core/__init__.py @@ -8,10 +8,12 @@ from .about import __version__ from .activity_handler import ActivityHandler +from .auto_save_state_middleware import AutoSaveStateMiddleware from .bot_assert import BotAssert from .bot_adapter import BotAdapter from .bot_framework_adapter import BotFrameworkAdapter, BotFrameworkAdapterSettings from .bot_state import BotState +from .bot_state_set import BotStateSet from .bot_telemetry_client import BotTelemetryClient from .card_factory import CardFactory from .conversation_state import ConversationState @@ -33,11 +35,13 @@ __all__ = [ "ActivityHandler", "AnonymousReceiveMiddleware", + "AutoSaveStateMiddleware", "BotAdapter", "BotAssert", "BotFrameworkAdapter", "BotFrameworkAdapterSettings", "BotState", + "BotStateSet", "BotTelemetryClient", "calculate_change_hash", "CardFactory", diff --git a/libraries/botbuilder-core/botbuilder/core/auto_save_state_middleware.py b/libraries/botbuilder-core/botbuilder/core/auto_save_state_middleware.py new file mode 100644 index 000000000..3e42664fc --- /dev/null +++ b/libraries/botbuilder-core/botbuilder/core/auto_save_state_middleware.py @@ -0,0 +1,29 @@ +from typing import Awaitable, Callable, List, Union + +from .bot_state import BotState +from .bot_state_set import BotStateSet +from .middleware_set import Middleware +from .turn_context import TurnContext + + +class AutoSaveStateMiddleware(Middleware): + def __init__(self, bot_states: Union[List[BotState], BotStateSet] = None): + if bot_states is None: + bot_states = [] + if isinstance(bot_states, BotStateSet): + self.bot_state_set: BotStateSet = bot_states + else: + self.bot_state_set: BotStateSet = BotStateSet(bot_states) + + def add(self, bot_state: BotState) -> "AutoSaveStateMiddleware": + if bot_state is None: + raise TypeError("Expected BotState") + + self.bot_state_set.add(bot_state) + return self + + async def on_process_request( + self, context: TurnContext, logic: Callable[[TurnContext], Awaitable] + ): + await logic() + await self.bot_state_set.save_all_changes(context, False) diff --git a/libraries/botbuilder-core/botbuilder/core/bot_state_set.py b/libraries/botbuilder-core/botbuilder/core/bot_state_set.py new file mode 100644 index 000000000..8a86aaba0 --- /dev/null +++ b/libraries/botbuilder-core/botbuilder/core/bot_state_set.py @@ -0,0 +1,29 @@ +from asyncio import wait +from typing import List +from .bot_state import BotState +from .turn_context import TurnContext + + +class BotStateSet: + def __init__(self, bot_states: List[BotState]): + self.bot_states = list(bot_states) + + def add(self, bot_state: BotState) -> "BotStateSet": + if bot_state is None: + raise TypeError("Expected BotState") + + self.bot_states.append(bot_state) + return self + + async def load_all(self, turn_context: TurnContext, force: bool = False): + await wait( + [bot_state.load(turn_context, force) for bot_state in self.bot_states] + ) + + async def save_all_changes(self, turn_context: TurnContext, force: bool = False): + await wait( + [ + bot_state.save_changes(turn_context, force) + for bot_state in self.bot_states + ] + ) diff --git a/libraries/botbuilder-core/tests/test_auto_save_middleware.py b/libraries/botbuilder-core/tests/test_auto_save_middleware.py new file mode 100644 index 000000000..46545ff58 --- /dev/null +++ b/libraries/botbuilder-core/tests/test_auto_save_middleware.py @@ -0,0 +1,113 @@ +import aiounittest +from botbuilder.core import AutoSaveStateMiddleware, BotState, TurnContext +from botbuilder.core.adapters import TestAdapter +from botbuilder.schema import Activity + + +async def aux_func(): + return + + +class BotStateMock(BotState): + def __init__(self, state): # pylint: disable=super-init-not-called + self.state = state + self.assert_force = False + self.read_called = False + self.write_called = False + + async def load(self, turn_context: TurnContext, force: bool = False) -> None: + assert turn_context is not None, "BotStateMock.load() not passed context." + if self.assert_force: + assert force, "BotStateMock.load(): force not set." + self.read_called = True + + async def save_changes( + self, turn_context: TurnContext, force: bool = False + ) -> None: + assert ( + turn_context is not None + ), "BotStateMock.save_changes() not passed context." + if self.assert_force: + assert force, "BotStateMock.save_changes(): force not set." + self.write_called = True + + def get_storage_key(self, turn_context: TurnContext) -> str: + return "" + + +class TestAutoSaveMiddleware(aiounittest.AsyncTestCase): + async def test_should_add_and_call_load_all_on_single_plugin(self): + adapter = TestAdapter() + context = TurnContext(adapter, Activity()) + foo_state = BotStateMock({"foo": "bar"}) + bot_state_set = AutoSaveStateMiddleware().add(foo_state) + await bot_state_set.bot_state_set.load_all(context) + + async def test_should_add_and_call_load_all_on_multiple_plugins(self): + adapter = TestAdapter() + context = TurnContext(adapter, Activity()) + foo_state = BotStateMock({"foo": "bar"}) + bar_state = BotStateMock({"bar": "foo"}) + bot_state_set = AutoSaveStateMiddleware([foo_state, bar_state]) + await bot_state_set.bot_state_set.load_all(context) + + async def test_should_add_and_call_save_all_changes_on_a_single_plugin(self): + adapter = TestAdapter() + context = TurnContext(adapter, Activity()) + foo_state = BotStateMock({"foo": "bar"}) + bot_state_set = AutoSaveStateMiddleware().add(foo_state) + await bot_state_set.bot_state_set.save_all_changes(context) + assert foo_state.write_called, "write not called for plugin." + + async def test_should_add_and_call_save_all_changes_on_multiple_plugins(self): + adapter = TestAdapter() + context = TurnContext(adapter, Activity()) + foo_state = BotStateMock({"foo": "bar"}) + bar_state = BotStateMock({"bar": "foo"}) + autosave_middleware = AutoSaveStateMiddleware([foo_state, bar_state]) + await autosave_middleware.bot_state_set.save_all_changes(context) + assert ( + foo_state.write_called or bar_state.write_called + ), "write not called for either plugin." + assert foo_state.write_called, "write not called for 'foo_state' plugin." + assert bar_state.write_called, "write not called for 'bar_state' plugin." + + async def test_should_pass_force_flag_through_in_load_all_call(self): + adapter = TestAdapter() + context = TurnContext(adapter, Activity()) + foo_state = BotStateMock({"foo": "bar"}) + foo_state.assert_force = True + autosave_middleware = AutoSaveStateMiddleware().add(foo_state) + await autosave_middleware.bot_state_set.load_all(context, True) + + async def test_should_pass_force_flag_through_in_save_all_changes_call(self): + adapter = TestAdapter() + context = TurnContext(adapter, Activity()) + foo_state = BotStateMock({"foo": "bar"}) + foo_state.assert_force = True + autosave_middleware = AutoSaveStateMiddleware().add(foo_state) + await autosave_middleware.bot_state_set.save_all_changes(context, True) + + async def test_should_work_as_a_middleware_plugin(self): + adapter = TestAdapter() + context = TurnContext(adapter, Activity()) + foo_state = BotStateMock({"foo": "bar"}) + autosave_middleware = AutoSaveStateMiddleware().add(foo_state) + await autosave_middleware.on_process_request(context, aux_func) + assert foo_state.write_called, "save_all_changes() not called." + + async def test_should_support_plugins_passed_to_constructor(self): + adapter = TestAdapter() + context = TurnContext(adapter, Activity()) + foo_state = BotStateMock({"foo": "bar"}) + autosave_middleware = AutoSaveStateMiddleware().add(foo_state) + await autosave_middleware.on_process_request(context, aux_func) + assert foo_state.write_called, "save_all_changes() not called." + + async def test_should_not_add_any_bot_state_on_construction_if_none_are_passed_in( + self + ): + middleware = AutoSaveStateMiddleware() + assert ( + not middleware.bot_state_set.bot_states + ), "should not have added any BotState." diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/number_prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/number_prompt.py index 54d6f15dd..cddb174c9 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/number_prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/number_prompt.py @@ -3,9 +3,9 @@ from typing import Callable, Dict -from babel.numbers import parse_decimal from recognizers_number import recognize_number from recognizers_text import Culture, ModelResult +from babel.numbers import parse_decimal from botbuilder.core.turn_context import TurnContext from botbuilder.schema import ActivityTypes From 9a5667c085696b0517436efba797177733795f3b Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Thu, 8 Aug 2019 18:40:50 -0700 Subject: [PATCH 2/5] fixed pylint --- libraries/botbuilder-core/tests/test_auto_save_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/tests/test_auto_save_middleware.py b/libraries/botbuilder-core/tests/test_auto_save_middleware.py index 46545ff58..873afb635 100644 --- a/libraries/botbuilder-core/tests/test_auto_save_middleware.py +++ b/libraries/botbuilder-core/tests/test_auto_save_middleware.py @@ -31,7 +31,7 @@ async def save_changes( assert force, "BotStateMock.save_changes(): force not set." self.write_called = True - def get_storage_key(self, turn_context: TurnContext) -> str: + def get_storage_key(self, turn_context: TurnContext) -> str: # pylint: unused-argument return "" From f09677b30f441381f69cbe2824c6c7efaced1bcd Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Thu, 8 Aug 2019 18:41:26 -0700 Subject: [PATCH 3/5] fixed pylint --- libraries/botbuilder-core/tests/test_auto_save_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/tests/test_auto_save_middleware.py b/libraries/botbuilder-core/tests/test_auto_save_middleware.py index 873afb635..fb1609abd 100644 --- a/libraries/botbuilder-core/tests/test_auto_save_middleware.py +++ b/libraries/botbuilder-core/tests/test_auto_save_middleware.py @@ -31,7 +31,7 @@ async def save_changes( assert force, "BotStateMock.save_changes(): force not set." self.write_called = True - def get_storage_key(self, turn_context: TurnContext) -> str: # pylint: unused-argument + def get_storage_key(self, turn_context: TurnContext) -> str: # pylint: disable=unused-argument return "" From d513d13a4e96615fad76c9f98dc868d6f814537a Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Thu, 8 Aug 2019 20:00:44 -0700 Subject: [PATCH 4/5] fixed black --- libraries/botbuilder-core/tests/test_auto_save_middleware.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/tests/test_auto_save_middleware.py b/libraries/botbuilder-core/tests/test_auto_save_middleware.py index fb1609abd..e5d7b594b 100644 --- a/libraries/botbuilder-core/tests/test_auto_save_middleware.py +++ b/libraries/botbuilder-core/tests/test_auto_save_middleware.py @@ -31,7 +31,9 @@ async def save_changes( assert force, "BotStateMock.save_changes(): force not set." self.write_called = True - def get_storage_key(self, turn_context: TurnContext) -> str: # pylint: disable=unused-argument + def get_storage_key( + self, turn_context: TurnContext + ) -> str: # pylint: disable=unused-argument return "" From 0f397493f36a27e2bfc8a535ac8767f0ba261a0c Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Thu, 8 Aug 2019 20:02:26 -0700 Subject: [PATCH 5/5] Moved pylint directive modified by black --- libraries/botbuilder-core/tests/test_auto_save_middleware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/botbuilder-core/tests/test_auto_save_middleware.py b/libraries/botbuilder-core/tests/test_auto_save_middleware.py index e5d7b594b..d63d84764 100644 --- a/libraries/botbuilder-core/tests/test_auto_save_middleware.py +++ b/libraries/botbuilder-core/tests/test_auto_save_middleware.py @@ -32,8 +32,8 @@ async def save_changes( self.write_called = True def get_storage_key( - self, turn_context: TurnContext - ) -> str: # pylint: disable=unused-argument + self, turn_context: TurnContext # pylint: disable=unused-argument + ) -> str: return ""