Skip to content

Commit aa3a072

Browse files
committed
SAFEKEEPING Checkin to unblock congysu
1 parent 818e811 commit aa3a072

3 files changed

Lines changed: 300 additions & 0 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
from abc import ABC, abstractmethod
4+
from .dialog_context import DialogContext
5+
6+
from botbuilder.core.turn_context import TurnContext
7+
8+
9+
class Dialog(ABC):
10+
def __init__(self, dialog_id: str):
11+
if dialog_id == None || not dialog_id.strip():
12+
raise TypeError('Dialog(): dialogId cannot be None.')
13+
14+
self.telemetry_client = None; # TODO: Make this NullBotTelemetryClient()
15+
self.id = dialog_id;
16+
17+
@abstractmethod
18+
async def begin_dialog(self, dc: DialogContext, options: object = None):
19+
"""
20+
Method called when a new dialog has been pushed onto the stack and is being activated.
21+
:param dc: The dialog context for the current turn of conversation.
22+
:param options: (Optional) additional argument(s) to pass to the dialog being started.
23+
"""
24+
raise NotImplementedError()
25+
26+
async def continue_dialog(self, dc: DialogContext):
27+
"""
28+
Method called when an instance of the dialog is the "current" dialog and the
29+
user replies with a new activity. The dialog will generally continue to receive the user's
30+
replies until it calls either `end_dialog()` or `begin_dialog()`.
31+
If this method is NOT implemented then the dialog will automatically be ended when the user replies.
32+
:param dc: The dialog context for the current turn of conversation.
33+
:return:
34+
"""
35+
# By default just end the current dialog.
36+
return await dc.EndDialog(None);
37+
38+
async def resume_dialog(self, dc: DialogContext, reason: DialogReason, result: object):
39+
"""
40+
Method called when an instance of the dialog is being returned to from another
41+
dialog that was started by the current instance using `begin_dialog()`.
42+
If this method is NOT implemented then the dialog will be automatically ended with a call
43+
to `end_dialog()`. Any result passed from the called dialog will be passed
44+
to the current dialog's parent.
45+
:param dc: The dialog context for the current turn of conversation.
46+
:param reason: Reason why the dialog resumed.
47+
:param result: (Optional) value returned from the dialog that was called. The type of the value returned is dependent on the dialog that was called.
48+
:return:
49+
"""
50+
# By default just end the current dialog.
51+
return await dc.EndDialog(result);
52+
53+
async def reprompt_dialog(self, context: TurnContext, instance: DialogInstance):
54+
"""
55+
:param context:
56+
:return:
57+
"""
58+
# No-op by default
59+
return;
60+
61+
async def end_dialog(self, context: TurnContext, instance: DialogInstance):
62+
"""
63+
:param context:
64+
:return:
65+
"""
66+
# No-op by default
67+
return;
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .dialog_set import DialogSet
5+
from .dialog_state import DialogState
6+
from .dialog_turn_result import DialogTurnResult
7+
from .dialog_reason import DialogReason
8+
from botbuilder.core.turn_context import TurnContext
9+
10+
11+
class DialogContext():
12+
def __init__(self, dialogs: DialogSet, turn_context: TurnContext, state: DialogState):
13+
if dialogs is None:
14+
raise TypeError('DialogContext(): dialogs cannot be None.')
15+
if turn_context is None:
16+
raise TypeError('DialogContext(): turn_context cannot be None.')
17+
self.__turn_context = turn_context;
18+
self.__dialogs = dialogs;
19+
self.__id = dialog_id;
20+
self.__stack = state.dialog_stack;
21+
self.parent;
22+
23+
@property
24+
def dialogs(self):
25+
"""Gets the set of dialogs that can be called from this context.
26+
27+
:param:
28+
:return str:
29+
"""
30+
return self.__dialogs;
31+
32+
@property
33+
def context(self):
34+
"""Gets the context for the current turn of conversation.
35+
36+
:param:
37+
:return str:
38+
"""
39+
return self.__stack;
40+
41+
@property
42+
def stack(self):
43+
"""Gets the current dialog stack.
44+
45+
:param:
46+
:return str:
47+
"""
48+
return self.__stack;
49+
50+
51+
@property
52+
def active_dialog(self):
53+
"""Return the container link in the database.
54+
55+
:param:
56+
:return str:
57+
"""
58+
if (self.__stack && self.__stack.size() > 0):
59+
return self.__stack[0];
60+
return None;
61+
62+
63+
64+
async def begin_dialog(self, dialog_id: str, options: object = None):
65+
"""
66+
Pushes a new dialog onto the dialog stack.
67+
:param dialog_id: ID of the dialog to start..
68+
:param options: (Optional) additional argument(s) to pass to the dialog being started.
69+
"""
70+
if (not dialog_id):
71+
raise TypeError('Dialog(): dialogId cannot be None.')
72+
# Look up dialog
73+
dialog = find_dialog(dialog_id);
74+
if (not dialog):
75+
raise Exception("'DialogContext.begin_dialog(): A dialog with an id of '%s' wasn't found."
76+
" The dialog must be included in the current or parent DialogSet."
77+
" For example, if subclassing a ComponentDialog you can call add_dialog() within your constructor." % dialog_id);
78+
# Push new instance onto stack
79+
instance = new DialogInstance();
80+
{
81+
Id = dialogId,
82+
State = new Dictionary<string, object>(),
83+
};
84+
85+
stack.insert(0, instance);
86+
87+
# Call dialog's BeginAsync() method
88+
return await dialog.begin_dialog(this, options);
89+
90+
async def prompt(self, dialog_id: str, options: PromptOptions) -> DialogTurnResult:
91+
"""
92+
Helper function to simplify formatting the options for calling a prompt dialog. This helper will
93+
take a `PromptOptions` argument and then call.
94+
:param dialog_id: ID of the prompt to start.
95+
:param options: Contains a Prompt, potentially a RetryPrompt and if using ChoicePrompt, Choices.
96+
:return:
97+
"""
98+
if (not dialog_id):
99+
raise TypeError('DialogContext.prompt(): dialogId cannot be None.');
100+
101+
if (not options):
102+
raise TypeError('DialogContext.prompt(): options cannot be None.');
103+
104+
return await begin_dialog(dialog_id, options);
105+
106+
async def continue_dialog(self, dc: DialogContext, reason: DialogReason, result: object):
107+
"""
108+
Continues execution of the active dialog, if there is one, by passing the context object to
109+
its `Dialog.continue_dialog()` method. You can check `turn_context.responded` after the call completes
110+
to determine if a dialog was run and a reply was sent to the user.
111+
:return:
112+
"""
113+
# Check for a dialog on the stack
114+
if not active_dialog:
115+
# Look up dialog
116+
dialog = find_dialog(active_dialog.id);
117+
if not dialog:
118+
raise Exception("DialogContext.continue_dialog(): Can't continue dialog. A dialog with an id of '%s' wasn't found." % active_dialog.id);
119+
120+
# Continue execution of dialog
121+
return await dialog.continue_dialog(self);
122+
else:
123+
return new DialogTurnResult(DialogTurnStatus.Empty);
124+
125+
async def end_dialog(self, context: TurnContext, instance: DialogInstance):
126+
"""
127+
Ends a dialog by popping it off the stack and returns an optional result to the dialog's
128+
parent. The parent dialog is the dialog that started the dialog being ended via a call to
129+
either "begin_dialog" or "prompt".
130+
The parent dialog will have its `Dialog.resume_dialog()` method invoked with any returned
131+
result. If the parent dialog hasn't implemented a `resume_dialog()` method then it will be
132+
automatically ended as well and the result passed to its parent. If there are no more
133+
parent dialogs on the stack then processing of the turn will end.
134+
:param result: (Optional) result to pass to the parent dialogs.
135+
:return:
136+
"""
137+
await end_active_dialog(DialogReason.EndCalled);
138+
139+
# Resume previous dialog
140+
if not active_dialog:
141+
# Look up dialog
142+
dialog = find_dialog(active_dialog.id);
143+
if not dialog:
144+
raise Exception("DialogContext.EndDialogAsync(): Can't resume previous dialog. A dialog with an id of '%s' wasn't found." % active_dialog.id);
145+
146+
# Return result to previous dialog
147+
return await dialog.resume_dialog(self, DialogReason.EndCalled, result);
148+
else:
149+
return new DialogTurnResult(DialogTurnStatus.Complete, result);
150+
151+
152+
async def cancel_all_dialogs(self):
153+
"""
154+
Deletes any existing dialog stack thus cancelling all dialogs on the stack.
155+
:param result: (Optional) result to pass to the parent dialogs.
156+
:return:
157+
"""
158+
if (len(stack) > 0):
159+
while (len(stack) > 0):
160+
await end_active_dialog(DialogReason.CancelCalled);
161+
return DialogTurnResult(DialogTurnStatus.Cancelled);
162+
else:
163+
return DialogTurnResult(DialogTurnStatus.Empty);
164+
165+
async def find_dialog(self, dialog_id: str) -> Dialog:
166+
"""
167+
If the dialog cannot be found within the current `DialogSet`, the parent `DialogContext`
168+
will be searched if there is one.
169+
:param dialog_id: ID of the dialog to search for.
170+
:return:
171+
"""
172+
dialog = dialogs.find(dialog_id);
173+
if (not dialog && parent != None):
174+
dialog = parent.find_dialog(dialog_id);
175+
return dialog;
176+
177+
async def replace_dialog(self) -> DialogTurnResult:
178+
"""
179+
Ends the active dialog and starts a new dialog in its place. This is particularly useful
180+
for creating loops or redirecting to another dialog.
181+
:param dialog_id: ID of the dialog to search for.
182+
:param options: (Optional) additional argument(s) to pass to the new dialog.
183+
:return:
184+
"""
185+
# End the current dialog and giving the reason.
186+
await end_active_dialog(DialogReason.ReplaceCalled);
187+
188+
# Start replacement dialog
189+
return await begin_dialog(dialogId, options);
190+
191+
async def reprompt_dialog(self):
192+
"""
193+
Calls reprompt on the currently active dialog, if there is one. Used with Prompts that have a reprompt behavior.
194+
:return:
195+
"""
196+
# Check for a dialog on the stack
197+
if active_dialog != None:
198+
# Look up dialog
199+
dialog = find_dialog(active_dialog.id);
200+
if not dialog:
201+
raise Exception("DialogSet.reprompt_dialog(): Can't find A dialog with an id of '%s'." % active_dialog.id);
202+
203+
# Ask dialog to re-prompt if supported
204+
await dialog.reprompt_dialog(context, active_dialog);
205+
206+
async def end_active_dialog(reason: DialogReason):
207+
instance = active_dialog;
208+
if instance != None:
209+
# Look up dialog
210+
dialog = find_dialog(instance.id);
211+
if not dialog:
212+
# Notify dialog of end
213+
await dialog.end_dialog(context, instance, reason);
214+
215+
# Pop dialog off stack
216+
stack.pop());
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
from enum import Enum
4+
5+
class DialogReason(Enum):
6+
# A dialog is being started through a call to `DialogContext.begin()`.
7+
BeginCalled = 1
8+
# A dialog is being continued through a call to `DialogContext.continue_dialog()`.
9+
ContinueCalled = 2
10+
# A dialog ended normally through a call to `DialogContext.end_dialog()`.
11+
EndCalled = 3
12+
# A dialog is ending because it's being replaced through a call to `DialogContext.replace_dialog()`.
13+
ReplaceCalled = 4
14+
# A dialog was cancelled as part of a call to `DialogContext.cancel_all_dialogs()`.
15+
CancelCalled = 5
16+
# A step was advanced through a call to `WaterfallStepContext.next()`.
17+
NextCalled = 6

0 commit comments

Comments
 (0)