8000 Merge pull request #79 from Microsoft/v-stgum/testAdapter · Pucadopr/botbuilder-python@4301de4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4301de4

Browse files
authored
Merge pull request microsoft#79 from Microsoft/v-stgum/testAdapter
add TestAdapter and State; refactor BotAdapter, BotContext, BotFrameworkAdapter
2 parents ca58b91 + 82c0cab commit 4301de4

27 files changed

+1785
-103
lines changed

libraries/botbuilder-core/botbuilder/core/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,28 @@
1010
from .bot_adapter import BotAdapter
1111
from .bot_framework_adapter import BotFrameworkAdapter, BotFrameworkAdapterSettings
1212
from .bot_context import BotContext
13+
from .bot_state import BotState
14+
from .conversation_state import ConversationState
15+
from .memory_storage import MemoryStorage
1316
from .middleware_set import AnonymousReceiveMiddleware, Middleware, MiddlewareSet
17+
from .storage import Storage, StoreItem, StorageKeyFactory, calculate_change_hash
18+
from .test_adapter import TestAdapter
19+
from .user_state import UserState
1420

1521
__all__ = ['AnonymousReceiveMiddleware',
1622
'BotAdapter',
1723
'BotContext',
1824
'BotFrameworkAdapter',
1925
'BotFrameworkAdapterSettings',
26+
'BotState',
27+
'calculate_change_hash',
28+
'ConversationState',
29+
'MemoryStorage',
2030
'Middleware',
2131
'MiddlewareSet',
32+
'Storage',
33+
'StorageKeyFactory',
34+
'StoreItem',
35+
'TestAdapter',
36+
'UserState',
2237
'__version__',]

libraries/botbuilder-core/botbuilder/core/about.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
14
__title__ = 'botbuilder-core'
25
__version__ = '4.0.0.a4'
36
__uri__ = 'https://www.github.com/Microsoft/botbuilder-python'

libraries/botbuilder-core/botbuilder/core/bot_adapter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def __init__(self):
1414
self._middleware = MiddlewareSet()
1515

1616
@abstractmethod
17-
async def send_activity(self, activities: List[Activity]):
17+
async def send_activities(self, context: BotContext, activities: List[Activity]):
1818
"""
1919
Sends a set of activities to the user. An array of responses from the server will be returned.
2020
:param activities:
@@ -23,7 +23,7 @@ async def send_activity(self, activities: List[Activity]):
2323
raise NotImplementedError()
2424

2525
@abstractmethod
26-
async def update_activity(self, activity: Activity):
26+
async def update_activity(self, context: BotContext, activity: Activity):
2727
"""
2828
Replaces an existing activity.
2929
:param activity:
@@ -32,7 +32,7 @@ async def update_activity(self, activity: Activity):
3232
raise NotImplementedError()
3333

3434
@abstractmethod
35-
async def delete_activity(self, reference: ConversationReference):
35+
async def delete_activity(self, context: BotContext, reference: ConversationReference):
3636
"""
3737
Deletes an existing activity.
3838
:param reference:

libraries/botbuilder-core/botbuilder/core/bot_context.py

Lines changed: 145 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,90 @@
22
# Licensed under the MIT License.
33

44
import asyncio
5-
import sys
6-
from copy import deepcopy, copy
5+
from copy import copy
76
from uuid import uuid4
8-
from typing import List, Callable, Iterable, Tuple
9-
from botbuilder.schema import Activity, ActivityTypes, ConversationReference, ResourceResponse
10-
11-
# from .bot_adapter import BotAdapter
7+
from typing import List, Callable, Union
8+
from botbuilder.schema import Activity, ConversationReference, ResourceResponse
129

1310

1411
class BotContext(object):
15-
def __init__(self, adapter, request: Activity):
16-
self.adapter = adapter
17-
self.request: Activity = request
18-
self.responses: List[Activity] = []
19-
self._services: dict = {}
20-
self._responded: bool = False
21-
self._on_send_activity: Callable[[]] = []
22-
self._on_update_activity: Callable[[]] = []
23-
self._on_delete_activity: Callable[[]] = []
24-
25-
if not self.request:
12+
def __init__(self, adapter_or_context, request: Activity=None):
13+
"""
14+
Creates a new BotContext instance.
15+
:param adapter_or_context:
16+
:param request:
17+
"""
18+
if isinstance(adapter_or_context, BotContext):
19+
adapter_or_context.copy_to(self)
20+
else:
21+
self.adapter = adapter_or_context
22+
self._activity = request
23+
self.responses: List[Activity] = []
24+
self._services: dict = {}
25+
self._on_send_activities: Callable[[]] = []
26+
self._on_update_activity: Callable[[]] = []
27+
self._on_delete_activity: Callable[[]] = []
28+
self._responded = {'responded': False}
29+
30+
if self.adapter is None:
31+
raise TypeError('BotContext must be instantiated with an adapter.')
32+
if self.activity is None:
2633
raise TypeError('BotContext must be instantiated with a request parameter of type Activity.')
2734

35+
def copy_to(self, context: 'BotContext') -> None:
36+
"""
37+
Called when this TurnContext instance is passed into the constructor of a new TurnContext
38+
instance. Can be overridden in derived classes.
39+
:param context:
40+
:return:
41+
"""
42+
for attribute in ['adapter', 'activity', '_responded', '_services',
43+
'_on_send_activities', '_on_update_activity', '_on_delete_activity']:
44+
setattr(context, attribute, getattr(self, attribute))
45+
46+
@property
47+
def activity(self):
48+
"""
49+
The received activity.
50+
:return:
51+
"""
52+
return self._activity
53+
54+
@activity.setter
55+
def activity(self, value):
56+
"""
57+
Used to set BotContext._activity when a context object is created. Only takes instances of Activities.
58+
:param value:
59+
:return:
60+
"""
61+
if not isinstance(value, Activity):
62+
raise TypeError('BotContext: cannot set `activity` to a type other than Activity.')
63+
else:
64+
self._activity = value
65+
66+
@property
67+
def responded(self):
68+
"""
69+
If `true` at least one response has been sent for the current turn of conversation.
70+
:return:
71+
"""
72+
return self._responded['responded']
73+
74+
@responded.setter
75+
def responded(self, value):
76+
if not value:
77+
raise ValueError('BotContext: cannot set BotContext.responded to False.')
78+
else:
79+
self._responded['responded'] = True
80+
81+
@property
82+
def services(self):
83+
"""
84+
Map of services and other values cached for the lifetime of the turn.
85+
:return:
86+
"""
87+
return self._services
88+
2889
def get(self, key: str) -> object:
2990
if not key or not isinstance(key, str):
3091
raise TypeError('"key" must be a valid string.')
@@ -55,44 +116,96 @@ def set(self, key: str, value: object) -> None:
55116

56117
self._services[key] = value
57118

58-
async def send_activity(self, *activity_or_text: Tuple[Activity, str]):
59-
reference = BotContext.get_conversation_reference(self.request)
119+
async def send_activity(self, *activity_or_text: Union[Activity, str]) -> ResourceResponse:
120+
"""
121+
Sends a single activity or message to the user.
122+
:param activity_or_text:
123+
:return:
124+
"""
125+
reference = BotContext.get_conversation_reference(self.activity)
60126
output = [BotContext.apply_conversation_reference(
61127
Activity(text=a, type='message') if isinstance(a, str) else a, reference)
62128
for a in activity_or_text]
63129
for activity in output:
64130
activity.input_hint = 'acceptingInput'
65131

66132
async def callback(context: 'BotContext', output):
67-
responses = await context.adapter.send_activity(output)
133+
responses = await context.adapter.send_activities(context, output)
68134
context._responded = True
69135
return responses
70136

71-
await self._emit(self._on_send_activity, output, callback(self, output))
137+
await self._emit(self._on_send_activities, output, callback(self, output))
72138

73139
async def update_activity(self, activity: Activity):
74-
return asyncio.ensure_future(self._emit(self._on_update_activity,
75-
activity,
76-
self.adapter.update_activity(activity)))
140+
"""
141+
Replaces an existing activity.
142+
:param activity:
143+
:return:
144+
"""
145+
return await self._emit(self._on_update_activity, activity, self.adapter.update_activity(self, activity))
77146

78-
@staticmethod
79-
async def _emit(plugins, arg, logic):
147+
async def delete_activity(self, id_or_reference: Union[str, ConversationReference]):
148+
"""
149+
Deletes an existing activity.
150+
:param id_or_reference:
151+
:return:
152+
"""
153+
if type(id_or_reference) == str:
154+
reference = BotContext.get_conversation_reference(self.activity)
155+
reference.activity_id = id_or_reference
156+
else:
157+
reference = id_or_reference
158+
return await self._emit(self._on_delete_activity, reference, self.adapter.delete_activity(self, reference))
159+
160+
def on_send_activities(self, handler) -> 'BotContext':
161+
"""
162+
Registers a handler to be notified of and potentially intercept the sending of activities.
163+
:param handler:
164+
:return:
165+
"""
166+
self._on_send_activities.append(handler)
167+
return self
168+
169+
def on_update_activity(self, handler) -> 'BotContext':
170+
"""
171+
Registers a handler to be notified of and potentially intercept an activity being updated.
172+
:param handler:
173+
:return:
174+
"""
175+
self._on_update_activity.append(handler)
176+
return self
177+
178+
def on_delete_activity(self, handler) -> 'BotContext':
179+
"""
180+
Registers a handler to be notified of and potentially intercept an activity being deleted.
181+
:param handler:
182+
:return:
183+
"""
184+
self._on_delete_activity.append(handler)
185+
return self
186+
187+
async def _emit(self, plugins, arg, logic):
80188
handlers = copy(plugins)
81189

82190
async def emit_next(i: int):
191+
context = self
83192
try:
84193
if i < len(handlers):
85-
await handlers[i](arg, emit_next(i + 1))
86-
asyncio.ensure_future(logic)
194+
async def next_handler():
195+
await emit_next(i + 1)
196+
await handlers[i](context, arg, next_handler)
197+
87198
except Exception as e:
88199
raise e
89200
await emit_next(0)
201+
# This should be changed to `return await logic()`
202+
return await logic
90203

91204
@staticmethod
92205
def get_conversation_reference(activity: Activity) -> ConversationReference:
93206
"""
94207
Returns the conversation reference for an activity. This can be saved as a plain old JSON
95-
bject and then later used to message the user proactively.
208+
object and then later used to message the user proactively.
96209
97210
Usage Example:
98211
reference = BotContext.get_conversation_reference(context.request)
@@ -119,9 +232,9 @@ def apply_conversation_reference(activity: Activity,
119232
:param is_incoming:
120233
:return:
121234
"""
122-
activity.channel_id=reference.channel_id
123-
activity.service_url=reference.service_url
124-
activity.conversation=reference.conversation
235+
activity.channel_id = reference.channel_id
236+
activity.service_url = reference.service_url
237+
activity.conversation = reference.conversation
125238
if is_incoming:
126239
activity.from_property = reference.user
127240
activity.recipient = reference.bot
@@ -134,4 +247,3 @@ def apply_conversation_reference(activity: Activity,
134247
activity.reply_to_id = reference.activity_id
135248

136249
return activity
137-

0 commit comments

Comments
 (0)
0