diff --git a/libraries/botbuilder-core/botbuilder/core/__init__.py b/libraries/botbuilder-core/botbuilder/core/__init__.py index f5e6c0ef8..832e91242 100644 --- a/libraries/botbuilder-core/botbuilder/core/__init__.py +++ b/libraries/botbuilder-core/botbuilder/core/__init__.py @@ -23,6 +23,7 @@ from .message_factory import MessageFactory from .middleware_set import AnonymousReceiveMiddleware, Middleware, MiddlewareSet from .null_telemetry_client import NullTelemetryClient +from .private_conversation_state import PrivateConversationState from .recognizer import Recognizer from .recognizer_result import RecognizerResult, TopIntent from .show_typing_middleware import ShowTypingMiddleware @@ -55,6 +56,7 @@ "Middleware", "MiddlewareSet", "NullTelemetryClient", + "PrivateConversationState", "Recognizer", "RecognizerResult", "Severity", diff --git a/libraries/botbuilder-core/botbuilder/core/private_conversation_state.py b/libraries/botbuilder-core/botbuilder/core/private_conversation_state.py new file mode 100644 index 000000000..6b13bc5f5 --- /dev/null +++ b/libraries/botbuilder-core/botbuilder/core/private_conversation_state.py @@ -0,0 +1,36 @@ +from .bot_state import BotState +from .turn_context import TurnContext +from .storage import Storage + + +class PrivateConversationState(BotState): + def __init__(self, storage: Storage, namespace: str = ""): + async def aux_func(context: TurnContext) -> str: + nonlocal self + return await self.get_storage_key(context) + + self.namespace = namespace + super().__init__(storage, aux_func) + + def get_storage_key(self, turn_context: TurnContext) -> str: + activity = turn_context.activity + channel_id = activity.channel_id if activity is not None else None + + if not channel_id: + raise Exception("missing activity.channel_id") + + if activity and activity.conversation and activity.conversation.id is not None: + conversation_id = activity.conversation.id + else: + raise Exception("missing activity.conversation.id") + + if ( + activity + and activity.from_property + and activity.from_property.id is not None + ): + user_id = activity.from_property.id + else: + raise Exception("missing activity.from_property.id") + + return f"{channel_id}/conversations/{ conversation_id }/users/{ user_id }/{ self.namespace }" diff --git a/libraries/botbuilder-core/tests/test_private_conversation_state.py b/libraries/botbuilder-core/tests/test_private_conversation_state.py new file mode 100644 index 000000000..fe477a72b --- /dev/null +++ b/libraries/botbuilder-core/tests/test_private_conversation_state.py @@ -0,0 +1,36 @@ +import aiounittest + +from botbuilder.core import MemoryStorage, TurnContext, PrivateConversationState +from botbuilder.core.adapters import TestAdapter +from botbuilder.schema import Activity, ChannelAccount, ConversationAccount + +RECEIVED_MESSAGE = Activity( + text="received", + type="message", + channel_id="test", + conversation=ConversationAccount(id="convo"), + from_property=ChannelAccount(id="user"), +) + + +class TestPrivateConversationState(aiounittest.AsyncTestCase): + async def test_should_load_and_save_state_from_storage(self): + storage = MemoryStorage() + adapter = TestAdapter() + context = TurnContext(adapter, RECEIVED_MESSAGE) + private_conversation_state = PrivateConversationState(storage) + + # Simulate a "Turn" in a conversation by loading the state, + # changing it and then saving the changes to state. + await private_conversation_state.load(context) + key = private_conversation_state.get_storage_key(context) + state = private_conversation_state.get(context) + assert state == {}, "State not loaded" + assert key, "Key not found" + state["test"] = "foo" + await private_conversation_state.save_changes(context) + + # Check the storage to see if the changes to state were saved. + items = await storage.read([key]) + assert key in items, "Saved state not found in storage." + assert items[key]["test"] == "foo", "Missing test value in stored state."