Skip to content

Commit 4a5afc2

Browse files
committed
Add unittest test, fix setup.py, misc stubs to get initial test running
1 parent aa3a072 commit 4a5afc2

12 files changed

Lines changed: 231 additions & 33 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from copy import copy
5+
from abc import ABC, abstractmethod
6+
from typing import Callable, List
7+
8+
from .turn_context import TurnContext
9+
10+
11+
class StatePropertyAccessor(ABC):
12+
@abstractmethod
13+
async def get(self, turnContext: TurnContext, default_value_factory = None):
14+
"""
15+
Get the property value from the source
16+
:param turn_context: Turn Context.
17+
:param default_value_factory: Function which defines the property value to be returned if no value has been set.
18+
19+
:return:
20+
"""
21+
raise NotImplementedError()
22+
23+
@abstractmethod
24+
async def delete(self, turnContext: TurnContext):
25+
"""
26+
Saves store items to storage.
27+
:param turn_context: Turn Context.
28+
:return:
29+
"""
30+
raise NotImplementedError()
31+
32+
@abstractmethod
33+
async def set(self, turnContext: TurnContext, value):
34+
"""
35+
Set the property value on the source.
36+
:param turn_context: Turn Context.
37+
:return:
38+
"""
39+
raise NotImplementedError()
40+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from abc import ABC
5+
6+
class StatePropertyAccessor(ABC):
7+
@property
8+
def name(self):
9+
raise NotImplementedError();

libraries/botbuilder-dialogs/botbuilder/dialogs/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,13 @@
77

88
from .about import __version__
99

10-
__all__ = [
10+
from .dialog_context import DialogContext
11+
from .dialog import Dialog
12+
from .dialog_set import DialogSet
13+
from .dialog_state import DialogState
14+
15+
__all__ = ['Dialog',
16+
'DialogContext',
17+
'DialogSet',
18+
'DialogState',
1119
'__version__']

libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33
from abc import ABC, abstractmethod
4-
from .dialog_context import DialogContext
54

65
from botbuilder.core.turn_context import TurnContext
6+
from .dialog_reason import DialogReason
77

88

99
class Dialog(ABC):
1010
def __init__(self, dialog_id: str):
11-
if dialog_id == None || not dialog_id.strip():
11+
if dialog_id == None or not dialog_id.strip():
1212
raise TypeError('Dialog(): dialogId cannot be None.')
1313

1414
self.telemetry_client = None; # TODO: Make this NullBotTelemetryClient()
15-
self.id = dialog_id;
15+
self.__id = dialog_id;
16+
17+
@property
18+
def id(self):
19+
return self.__id;
1620

1721
@abstractmethod
18-
async def begin_dialog(self, dc: DialogContext, options: object = None):
22+
async def begin_dialog(self, dc, options: object = None):
1923
"""
2024
Method called when a new dialog has been pushed onto the stack and is being activated.
2125
:param dc: The dialog context for the current turn of conversation.
2226
:param options: (Optional) additional argument(s) to pass to the dialog being started.
2327
"""
2428
raise NotImplementedError()
2529

26-
async def continue_dialog(self, dc: DialogContext):
30+
async def continue_dialog(self, dc):
2731
"""
2832
Method called when an instance of the dialog is the "current" dialog and the
2933
user replies with a new activity. The dialog will generally continue to receive the user's
@@ -35,7 +39,7 @@ async def continue_dialog(self, dc: DialogContext):
3539
# By default just end the current dialog.
3640
return await dc.EndDialog(None);
3741

38-
async def resume_dialog(self, dc: DialogContext, reason: DialogReason, result: object):
42+
async def resume_dialog(self, dc, reason: DialogReason, result: object):
3943
"""
4044
Method called when an instance of the dialog is being returned to from another
4145
dialog that was started by the current instance using `begin_dialog()`.
@@ -50,15 +54,16 @@ async def resume_dialog(self, dc: DialogContext, reason: DialogReason, result: o
5054
# By default just end the current dialog.
5155
return await dc.EndDialog(result);
5256

53-
async def reprompt_dialog(self, context: TurnContext, instance: DialogInstance):
57+
# TODO: instance is DialogInstance
58+
async def reprompt_dialog(self, context: TurnContext, instance):
5459
"""
5560
:param context:
5661
:return:
5762
"""
5863
# No-op by default
5964
return;
60-
61-
async def end_dialog(self, context: TurnContext, instance: DialogInstance):
65+
# TODO: instance is DialogInstance
66+
async def end_dialog(self, context: TurnContext, instance):
6267
"""
6368
:param context:
6469
:return:

libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_context.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4-
from .dialog_set import DialogSet
4+
55
from .dialog_state import DialogState
66
from .dialog_turn_result import DialogTurnResult
77
from .dialog_reason import DialogReason
8+
from .dialog import Dialog
89
from botbuilder.core.turn_context import TurnContext
910

10-
1111
class DialogContext():
12-
def __init__(self, dialogs: DialogSet, turn_context: TurnContext, state: DialogState):
12+
def __init__(self, dialog_set: object, turn_context: TurnContext, state: DialogState):
1313
if dialogs is None:
1414
raise TypeError('DialogContext(): dialogs cannot be None.')
15+
# TODO: Circular dependency with dialog_set: Check type.
1516
if turn_context is None:
1617
raise TypeError('DialogContext(): turn_context cannot be None.')
1718
self.__turn_context = turn_context;
@@ -55,7 +56,7 @@ def active_dialog(self):
5556
:param:
5657
:return str:
5758
"""
58-
if (self.__stack && self.__stack.size() > 0):
59+
if (self.__stack and self.__stack.size() > 0):
5960
return self.__stack[0];
6061
return None;
6162

@@ -76,18 +77,17 @@ async def begin_dialog(self, dialog_id: str, options: object = None):
7677
" The dialog must be included in the current or parent DialogSet."
7778
" For example, if subclassing a ComponentDialog you can call add_dialog() within your constructor." % dialog_id);
7879
# Push new instance onto stack
79-
instance = new DialogInstance();
80-
{
81-
Id = dialogId,
82-
State = new Dictionary<string, object>(),
83-
};
80+
instance = DialogInstance()
81+
instance.id = dialog_id
82+
instance.state = []
8483

8584
stack.insert(0, instance);
8685

8786
# Call dialog's BeginAsync() method
8887
return await dialog.begin_dialog(this, options);
8988

90-
async def prompt(self, dialog_id: str, options: PromptOptions) -> DialogTurnResult:
89+
# TODO: Fix options: PromptOptions instead of object
90+
async def prompt(self, dialog_id: str, options) -> DialogTurnResult:
9191
"""
9292
Helper function to simplify formatting the options for calling a prompt dialog. This helper will
9393
take a `PromptOptions` argument and then call.
@@ -103,7 +103,8 @@ async def prompt(self, dialog_id: str, options: PromptOptions) -> DialogTurnResu
103103

104104
return await begin_dialog(dialog_id, options);
105105

106-
async def continue_dialog(self, dc: DialogContext, reason: DialogReason, result: object):
106+
107+
async def continue_dialog(self, dc, reason: DialogReason, result: object):
107108
"""
108109
Continues execution of the active dialog, if there is one, by passing the context object to
109110
its `Dialog.continue_dialog()` method. You can check `turn_context.responded` after the call completes
@@ -120,9 +121,10 @@ async def continue_dialog(self, dc: DialogContext, reason: DialogReason, result:
120121
# Continue execution of dialog
121122
return await dialog.continue_dialog(self);
122123
else:
123-
return new DialogTurnResult(DialogTurnStatus.Empty);
124+
return DialogTurnResult(DialogTurnStatus.Empty);
124125

125-
async def end_dialog(self, context: TurnContext, instance: DialogInstance):
126+
# TODO: instance is DialogInstance
127+
async def end_dialog(self, context: TurnContext, instance):
126128
"""
127129
Ends a dialog by popping it off the stack and returns an optional result to the dialog's
128130
parent. The parent dialog is the dialog that started the dialog being ended via a call to
@@ -146,7 +148,7 @@ async def end_dialog(self, context: TurnContext, instance: DialogInstance):
146148
# Return result to previous dialog
147149
return await dialog.resume_dialog(self, DialogReason.EndCalled, result);
148150
else:
149-
return new DialogTurnResult(DialogTurnStatus.Complete, result);
151+
return DialogTurnResult(DialogTurnStatus.Complete, result);
150152

151153

152154
async def cancel_all_dialogs(self):
@@ -170,7 +172,7 @@ async def find_dialog(self, dialog_id: str) -> Dialog:
170172
:return:
171173
"""
172174
dialog = dialogs.find(dialog_id);
173-
if (not dialog && parent != None):
175+
if (not dialog and parent != None):
174176
dialog = parent.find_dialog(dialog_id);
175177
return dialog;
176178

@@ -201,16 +203,16 @@ async def reprompt_dialog(self):
201203
raise Exception("DialogSet.reprompt_dialog(): Can't find A dialog with an id of '%s'." % active_dialog.id);
202204

203205
# Ask dialog to re-prompt if supported
204-
await dialog.reprompt_dialog(context, active_dialog);
206+
await dialog.reprompt_dialog(context, active_dialog)
205207

206208
async def end_active_dialog(reason: DialogReason):
207209
instance = active_dialog;
208210
if instance != None:
209211
# Look up dialog
210-
dialog = find_dialog(instance.id);
212+
dialog = find_dialog(instance.id)
211213
if not dialog:
212214
# Notify dialog of end
213-
await dialog.end_dialog(context, instance, reason);
215+
await dialog.end_dialog(context, instance, reason)
214216

215217
# Pop dialog off stack
216-
stack.pop());
218+
stack.pop()
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
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+
from botbuilder.core.state_property_accessor import StatePropertyAccessor
10+
11+
12+
13+
class DialogSet():
14+
from .dialog import Dialog
15+
from .dialog_context import DialogContext
16+
17+
def __init__(self, dialog_state: StatePropertyAccessor):
18+
if dialog_state is None:
19+
raise TypeError('DialogSet(): dialog_state cannot be None.')
20+
self._dialog_state = dialog_state
21+
# self.__telemetry_client = NullBotTelemetryClient.Instance;
22+
23+
self._dialogs = []
24+
25+
26+
async def add(self, dialog: Dialog):
27+
"""
28+
Adds a new dialog to the set and returns the added dialog.
29+
:param dialog: The dialog to add.
30+
"""
31+
if not dialog:
32+
raise TypeError('DialogSet(): dialog cannot be None.')
33+
34+
if dialog.id in self._dialogs:
35+
raise TypeError("DialogSet.Add(): A dialog with an id of '%s' already added." % dialog.id)
36+
37+
# dialog.telemetry_client = this._telemetry_client;
38+
_dialogs[dialog.id] = dialog
39+
40+
return self
41+
42+
async def create_context(self, turn_context: TurnContext) -> DialogContext:
43+
BotAssert.context_not_null(turn_context)
44+
45+
if not _dialog_state:
46+
raise RuntimeError("DialogSet.CreateContextAsync(): DialogSet created with a null IStatePropertyAccessor.")
47+
48+
state = await _dialog_state.get(turn_context, lambda: DialogState())
49+
50+
return DialogContext(self, turn_context, state)
51+
52+
async def find(self, dialog_id: str) -> Dialog:
53+
"""
54+
Finds a dialog that was previously added to the set using add(dialog)
55+
:param dialog_id: ID of the dialog/prompt to look up.
56+
:return: The dialog if found, otherwise null.
57+
"""
58+
if (not dialog_id):
59+
raise TypeError('DialogContext.find(): dialog_id cannot be None.');
60+
61+
if dialog_id in _dialogs:
62+
return _dialogs[dialog_id]
63+
64+
return None
65+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
class DialogState():
5+
6+
def __init__(self, stack: []):
7+
if stack is None:
8+
raise TypeError('DialogState(): stack cannot be None.')
9+
self.__dialog_stack = stack
10+
11+
@property
12+
def dialog_stack(self):
13+
return __dialog_stack;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .dialog_turn_status import DialogTurnStatus
5+
6+
class DialogTurnResult():
7+
8+
def __init__(self, status: DialogTurnStatus, result:object = None):
9+
self.__status = status
10+
self.__result = result;
11+
12+
@property
13+
def status(self):
14+
return __status;
15+
16+
@property
17+
def result(self):
18+
return __result;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
from enum import Enum
4+
5+
class DialogTurnStatus(Enum):
6+
# Indicates that there is currently nothing on the dialog stack.
7+
Empty = 1
8+
9+
# Indicates that the dialog on top is waiting for a response from the user.
10+
Waiting = 2
11+
12+
# Indicates that the dialog completed successfully, the result is available, and the stack is empty.
13+
Complete = 3
14+
15+
# Indicates that the dialog was cancelled and the stack is empty.
16+
Cancelled = 4
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
msrest>=0.6.6
22
botframework-connector>=4.0.0.a6
33
botbuilder-schema>=4.0.0.a6
4+
botbuilder-core>=4.0.0.a6
45
requests>=2.18.1
56
PyJWT==1.5.3
67
cryptography==2.1.4

0 commit comments

Comments
 (0)