8000 DialogManager logic moved to DialogExtensions (#1753) · jayryanj/botbuilder-python@80f25c9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 80f25c9

Browse files
authored
DialogManager logic moved to DialogExtensions (microsoft#1753)
* DialogManager logic moved to DialogExtensions * missing await * fixing claims validation call * pylint
1 parent 2717884 commit 80f25c9

File tree

2 files changed

+138
-81
lines changed

2 files changed

+138
-81
lines changed

libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_extensions.py

Lines changed: 110 additions & 9 deletions
+
end_of_turn = True
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
)
1010
from botbuilder.core import BotAdapter, StatePropertyAccessor, TurnContext
1111
from botbuilder.core.skills import SkillHandler, SkillConversationReference
12+
import botbuilder.dialogs as dialogs # pylint: disable=unused-import
13+
from botbuilder.dialogs.memory import DialogStateManager
14+
from botbuilder.dialogs.dialog_context import DialogContext
15+
from botbuilder.dialogs.dialog_turn_result import DialogTurnResult
1216
from botbuilder.dialogs import (
13-
Dialog,
1417
DialogEvents,
1518
DialogSet,
1619
DialogTurnStatus,
@@ -21,7 +24,9 @@
2124
class DialogExtensions:
2225
@staticmethod
2326
async def run_dialog(
24-
dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor
27+
dialog: "dialogs.Dialog",
28+
turn_context: TurnContext,
29+
accessor: StatePropertyAccessor,
2530
):
2631
"""
2732
Creates a dialog stack and starts a dialog, pushing it onto the stack.
@@ -30,20 +35,71 @@ async def run_dialog(
3035
dialog_set = DialogSet(accessor)
3136
dialog_set.add(dialog)
3237

33-
dialog_context = await dialog_set.create_context(turn_context)
38+
dialog_context: DialogContext = await dialog_set.create_context(turn_context)
3439

40+
await DialogExtensions._internal_run(turn_context, dialog.id, dialog_context)
41+
42+
@staticmethod
43+
async def _internal_run(
44+
context: TurnContext, dialog_id: str, dialog_context: DialogContext
45+
) -> DialogTurnResult:
46+
# map TurnState into root dialog context.services
47+
for key, service in context.turn_state.items():
48+
dialog_context.services[key] = service
49+
50+
# get the DialogStateManager configuration
51+
dialog_state_manager = DialogStateManager(dialog_context)
52+
await dialog_state_manager.load_all_scopes()
53+
dialog_context.context.turn_state[
54+
dialog_state_manager.__class__.__name__
55+
] = dialog_state_manager
56+
57+
# Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn.
58+
59+
# NOTE: We loop around this block because each pass through we either complete the turn and break out of the
60+
# loop or we have had an exception AND there was an OnError action which captured the error. We need to
61+
# continue the turn based on the actions the OnError handler introduced.
62+
end_of_turn = False
63+
while not end_of_turn:
64+
try:
65+
dialog_turn_result = await DialogExtensions.__inner_run(
66+
context, dialog_id, dialog_context
67+
)
68+
69+
# turn successfully completed, break the loop
70
71+
except Exception as err:
72+
# fire error event, bubbling from the leaf.
73+
handled = await dialog_context.emit_event(
74+
DialogEvents.error, err, bubble=True, from_leaf=True
75+
)
76+
77+
if not handled:
78+
# error was NOT handled, throw the exception and end the turn. (This will trigger the
79+
# Adapter.OnError handler and end the entire dialog stack)
80+
raise
81+
82+
# save all state scopes to their respective botState locations.
83+
await dialog_state_manager.save_all_changes()
84+
85+
# return the redundant result because the DialogManager contract expects it
86+
return dialog_turn_result
87+
88+
@staticmethod
89+
async def __inner_run(
90+
turn_context: TurnContext, dialog_id: str, dialog_context: DialogContext
91+
) -> DialogTurnResult:
3592
# Handle EoC and Reprompt event from a parent bot (can be root bot to skill or skill to skill)
3693
if DialogExtensions.__is_from_parent_to_skill(turn_context):
3794
# Handle remote cancellation request from parent.
3895
if turn_context.activity.type == ActivityTypes.end_of_conversation:
3996
if not dialog_context.stack:
4097
# No dialogs to cancel, just return.
41-
return
98+
return DialogTurnResult(DialogTurnStatus.Empty)
4299

43100
# Send cancellation message to the dialog to ensure all the parents are canceled
44101
# in the right order.
45-
await dialog_context.cancel_all_dialogs()
46-
return
102+
return await dialog_context.cancel_all_dialogs(True)
47103

48104
# Handle a reprompt event sent from the parent.
49105
if (
@@ -52,15 +108,17 @@ async def run_dialog(
52108
):
53109
if not dialog_context.stack:
54110
# No dialogs to reprompt, just return.
55-
return
111+
return DialogTurnResult(DialogTurnStatus.Empty)
56112

57113
await dialog_context.reprompt_dialog()
58-
return
114+
return DialogTurnResult(DialogTurnStatus.Waiting)
59115

60116
# Continue or start the dialog.
61117
result = await dialog_context.continue_dialog()
62118
if result.status == DialogTurnStatus.Empty:
63-
result = await dialog_context.begin_dialog(dialog.id)
119+
result = await dialog_context.begin_dialog(dialog_id)
120+
121+
await DialogExtensions._send_state_snapshot_trace(dialog_context)
64122

65123
# Skills should send EoC when the dialog completes.
66124
if (
@@ -78,6 +136,8 @@ async def run_dialog(
78136
)
79137
await turn_context.send_activity(activity)
80138

139+
return result
140+
81141
@staticmethod
82142
def __is_from_parent_to_skill(turn_context: TurnContext) -> bool:
83143
if turn_context.turn_state.get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY):
@@ -88,6 +148,34 @@ def __is_from_parent_to_skill(turn_context: TurnContext) -> bool:
88148
claims_identity, ClaimsIdentity
89149
) and SkillValidation.is_skill_claim(claims_identity.claims)
90150

151+
@staticmethod
152+
async def _send_state_snapshot_trace(dialog_context: DialogContext):
153+
"""
154+
Helper to send a trace activity with a memory snapshot of the active dialog DC.
155+
:param dialog_context:
156+
:return:
157+
"""
158+
claims_identity = dialog_context.context.turn_state.get(
159+
BotAdapter.BOT_IDENTITY_KEY, None
160+
)
161+
trace_label = (
162+
"Skill State"
163+
if isinstance(claims_identity, ClaimsIdentity)
164+
and SkillValidation.is_skill_claim(claims_identity.claims)
165+
else "Bot State"
166+
)
167+
# send trace of memory
168+
snapshot = DialogExtensions._get_active_dialog_context(
169+
dialog_context
170+
).state.get_memory_snapshot()
171+
trace_activity = Activity.create_trace_activity(
172+
"BotState",
173+
"https://www.botframework.com/schemas/botState",
174+
snapshot,
175+
trace_label,
176+
)
177+
await dialog_context.context.send_activity(trace_activity)
178+
91179
@staticmethod
92180
def __send_eoc_to_parent(turn_context: TurnContext) -> bool:
93181
claims_identity = turn_context.turn_state.get(BotAdapter.BOT_IDENTITY_KEY)
@@ -111,3 +199,16 @@ def __send_eoc_to_parent(turn_context: TurnContext) -> bool:
111199
return True
112200

113201
return False
202+
203+
@staticmethod
204+
def _get_active_dialog_context(dialog_context: DialogContext) -> DialogContext:
205+
"""
206+
Recursively walk up the DC stack to find the active DC.
207+
:param dialog_context:
208+
:return:
209+
"""
210+
child = dialog_context.child
211+
if not child:
212+
return dialog_context
213+
214+
return DialogExtensions._get_active_dialog_context(child)

libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_manager.py

Lines changed: 28 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from datetime import datetime, timedelta
55
from threading import Lock
6+
from warnings import warn
67

78
from botbuilder.core import (
89
BotAdapter,
@@ -12,10 +13,7 @@
1213
TurnContext,
1314
)
1415
from botbuilder.core.skills import SkillConversationReference, SkillHandler
15-
from botbuilder.dialogs.memory import (
16-
DialogStateManager,
17-
DialogStateManagerConfiguration,
18-
)
16+
from botbuilder.dialogs.memory import DialogStateManagerConfiguration
1917
from botbuilder.schema import Activity, ActivityTypes, EndOfConversationCodes
2018
from botframework.connector.auth import (
2119
AuthenticationConstants,
@@ -27,6 +25,7 @@
2725
from .dialog import Dialog
2826
from .dialog_context import DialogContext
2927
from .dialog_events import DialogEvents
28+
from .dialog_extensions import DialogExtensions
3029
from .dialog_set import DialogSet
3130
from .dialog_state import DialogState
3231
from .dialog_manager_result import DialogManagerResult
@@ -142,60 +141,10 @@ async def on_turn(self, context: TurnContext) -> DialogManagerResult:
142141
# Create DialogContext
143142
dialog_context = DialogContext(self.dialogs, context, dialog_state)
144143

145-
# promote initial TurnState into dialog_context.services for contextual services
146-
for key, service in dialog_context.services.items():
147-
dialog_context.services[key] = service
148-
149-
# map TurnState into root dialog context.services
150-
for key, service in context.turn_state.items():
151-
dialog_context.services[key] = service
152-
153-
# get the DialogStateManager configuration
154-
dialog_state_manager = DialogStateManager(
155-
dialog_context, self.state_configuration
144+
# Call the common dialog "continue/begin" execution pattern shared with the classic RunAsync extension method
145+
turn_result = await DialogExtensions._internal_run( # pylint: disable=protected-access
146+
context, self._root_dialog_id, dialog_context
156147
)
157-
await dialog_state_manager.load_all_scopes()
158-
dialog_context.context.turn_state[
159-
dialog_state_manager.__class__.__name__
160-
] = dialog_state_manager
161-
162-
turn_result: DialogTurnResult = None
163-
164-
# Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn.
165-
166-
# NOTE: We loop around this block because each pass through we either complete the turn and break out of the
167-
# loop or we have had an exception AND there was an OnError action which captured the error. We need to
168-
# continue the turn based on the actions the OnError handler introduced.
169-
end_of_turn = False
170-
while not end_of_turn:
171-
try:
172-
claims_identity: ClaimsIdentity = context.turn_state.get(
173-
BotAdapter.BOT_IDENTITY_KEY, None
174-
)
175-
if isinstance(
176-
claims_identity, ClaimsIdentity
177-
) and SkillValidation.is_skill_claim(claims_identity.claims):
178-
# The bot is running as a skill.
179-
turn_result = await self.handle_skill_on_turn(dialog_context)
180-
else:
181-
# The bot is running as root bot.
182-
turn_result = await self.handle_bot_on_turn(dialog_context)
183-
184-
# turn successfully completed, break the loop
185-
end_of_turn = True
186-
except Exception as err:
187-
# fire error event, bubbling from the leaf.< 57D8 /div>
188-
handled = await dialog_context.emit_event(
189-
DialogEvents.error, err, bubble=True, from_leaf=True
190-
)
191-
192-
if not handled:
193-
# error was NOT handled, throw the exception and end the turn. (This will trigger the
194-
# Adapter.OnError handler and end the entire dialog stack)
195-
raise
196-
197-
# save all state scopes to their respective botState locations.
198-
await dialog_state_manager.save_all_changes()
199148

200149
# save BotState changes
201150
await bot_state_set.save_all_changes(dialog_context.context, False)
@@ -204,25 +153,22 @@ async def on_turn(self, context: TurnContext) -> DialogManagerResult:
204153

205154
@staticmethod
206155
async def send_state_snapshot_trace(
207-
dialog_context: DialogContext, trace_label: str
156+
dialog_context: DialogContext,
157+
trace_label: str = None, # pylint: disable=unused-argument
208158
):
209159
"""
210160
Helper to send a trace activity with a memory snapshot of the active dialog DC.
211161
:param dialog_context:
212162
:param trace_label:
213163
:return:
214164
"""
215-
# send trace of memory
216-
snapshot = DialogManager.get_active_dialog_context(
165+
warn(
166+
"This method will be deprecated as no longer is necesary",
167+
PendingDeprecationWarning,
168+
)
169+
await DialogExtensions._send_state_snapshot_trace( # pylint: disable=protected-access
217170
dialog_context
218-
).state.get_memory_snapshot()
219-
trace_activity = Activity.create_trace_activity(
220-
"BotState",
221-
"https://www.botframework.com/schemas/botState",
222-
snapshot,
223-
trace_label,
224171
)
225-
await dialog_context.context.send_activity(trace_activity)
226172

227173
@staticmethod
228174
def is_from_parent_to_skill(turn_context: TurnContext) -> bool:
@@ -246,11 +192,13 @@ def get_active_dialog_context(dialog_context: DialogContext) -> DialogContext:
246192
:param dialog_context:
247193
:return:
248194
"""
249-
child = dialog_context.child
250-
if not child:
251-
return dialog_context
252-
253-
return DialogManager.get_active_dialog_context(child)
195+
warn(
196+
"This method will be deprecated as no longer is necesary",
197+
PendingDeprecationWarning,
198+
)
199+
return DialogExtensions._get_active_dialog_context( # pylint: disable=protected-access
200+
dialog_context
201+
)
254202

255203
@staticmethod
256204
def should_send_end_of_conversation_to_parent(
@@ -294,6 +242,10 @@ def should_send_end_of_conversation_to_parent(
294242
async def handle_skill_on_turn(
295243
self, dialog_context: DialogContext
296244
) -> DialogTurnResult:
245+
warn(
246+
"This method will be deprecated as no longer is necesary",
247+
PendingDeprecationWarning,
248+
)
297249
# the bot is running as a skill.
298250
turn_context = dialog_context.context
299251

@@ -348,6 +300,10 @@ async def handle_skill_on_turn(
348300
async def handle_bot_on_turn(
349301
self, dialog_context: DialogContext
350302
) -> DialogTurnResult:
303+
warn(
304+
"This method will be deprecated as no longer is necesary",
305+
PendingDeprecationWarning,
306+
)
351307
# the bot is running as a root bot.
352308
if dialog_context.active_dialog is None:
353309
# start root dialog

0 commit comments

Comments
 (0)
0