8000 Added send_activities and updated the logic · itsmokha/botbuilder-python@0986575 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0986575

Browse files
committed
Added send_activities and updated the logic
1 parent f7ae133 commit 0986575

File tree

11 files changed

+120
-39
lines changed

11 files changed

+120
-39
lines changed

libraries/botbuilder-ai/tests/luis/null_adapter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ class NullAdapter(BotAdapter):
1212
This is a BotAdapter that does nothing on the Send operation, equivalent to piping to /dev/null.
1313
"""
1414

15-
async def send_activities(self, context: TurnContext, activities: List[Activity]):
15+
async def send_activities(
16+
self, context: TurnContext, activities: List[Activity]
17+
) -> List[ResourceResponse]:
1618
return [ResourceResponse()]
1719

1820
async def update_activity(self, context: TurnContext, activity: Activity):

libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ async def process_activity(
114114
activity.timestamp = activity.timestamp or datetime.utcnow()
115115
await self.run_pipeline(TurnContext(self, activity), logic)
116116

117-
async def send_activities(self, context, activities: List[Activity]):
117+
async def send_activities(
118+
self, context, activities: List[Activity]
119+
) -> List[ResourceResponse]:
118120
"""
119121
INTERNAL: called by the logic under test to send a set of activities. These will be buffered
120122
to the current `TestFlow` instance for comparison against the expected results.

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from abc import ABC, abstractmethod
55
from typing import List, Callable, Awaitable
6-
from botbuilder.schema import Activity, ConversationReference
6+
from botbuilder.schema import Activity, ConversationReference, ResourceResponse
77

88
from . import conversation_reference_extension
99
from .bot_assert import BotAssert
@@ -19,7 +19,9 @@ def __init__(
1919
self.on_turn_error = on_turn_error
2020

2121
@abstractmethod
22-
async def send_activities(self, context: TurnContext, activities: List[Activity]):
22+
async def send_activities(
23+
self, context: TurnContext, activities: List[Activity]
24+
) -> List[ResourceResponse]:
2325
"""
2426
Sends a set of activities to the user. An array of responses from the server will be returned.
2527
:param context:

libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
ConversationAccount,
1313
ConversationParameters,
1414
ConversationReference,
15+
ResourceResponse,
1516
TokenResponse,
1617
)
1718
from botframework.connector import Channels, EmulatorApiClient
@@ -330,9 +331,13 @@ async def delete_activity(
330331
except Exception as error:
331332
raise error
332333

333-
async def send_activities(self, context: TurnContext, activities: List[Activity]):
334+
async def send_activities(
335+
self, context: TurnContext, activities: List[Activity]
336+
) -> List[ResourceResponse]:
334337
try:
338+
responses: List[ResourceResponse] = []
335339
for activity in activities:
340+
response: ResourceResponse = None
336341
if activity.type == "delay":
337342
try:
338343
delay_in_ms = float(activity.value) / 1000
@@ -345,17 +350,38 @@ async def send_activities(self, context: TurnContext, activities: List[Activity]
345350
else:
346351
await asyncio.sleep(delay_in_ms)
347352
elif activity.type == "invokeResponse":
348-
context.turn_state.add(self._INVOKE_RESPONSE_KEY)
349-
elif activity.reply_to_id:
350-
client = self.create_connector_client(activity.service_url)
351-
await client.conversations.reply_to_activity(
352-
activity.conversation.id, activity.reply_to_id, activity
353-
)
353+
context.turn_state[self._INVOKE_RESPONSE_KEY] = activity
354354
else:
355+
if not getattr(activity, "service_url", None):
356+
raise TypeError(
357+
"BotFrameworkAdapter.send_activity(): service_url can not be None."
358+
)
359+
if (
360+
not hasattr(activity, "conversation")
361+
or not activity.conversation
362+
or not getattr(activity.conversation, "id", None)
363+
):
364+
raise TypeError(
365+
"BotFrameworkAdapter.send_activity(): conversation.id can not be None."
366+
)
367+
355368
client = self.create_connector_client(activity.service_url)
356-
await client.conversations.send_to_conversation(
357-
activity.conversation.id, activity
358-
)
369+
if activity.type == "trace" and activity.channel_id != "emulator":
370+
pass
371+
elif activity.reply_to_id:
372+
response = await client.conversations.reply_to_activity(
373+
activity.conversation.id, activity.reply_to_id, activity
374+
)
375+
else:
376+
response = await client.conversations.send_to_conversation(
377+
activity.conversation.id, activity
378+
)
379+
380+
if not response:
381+
response = ResourceResponse(activity.id or "")
382+
383+
responses.append(response)
384+
return responses
359385
except Exception as error:
360386
raise error
361387

libraries/botbuilder-core/botbuilder/core/transcript_logger.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import datetime
66
import copy
7+
import random
8+
import string
79
from queue import Queue
810
from abc import ABC, abstractmethod
911
from typing import Awaitable, Callable, List
@@ -57,8 +59,27 @@ async def send_activities_handler(
5759
):
5860
# Run full pipeline
5961
responses = await next_send()
60-
for activity in activities:
61-
self.log_activity(transcript, copy.copy(activity))
62+
for index, activity in enumerate(activities):
63+
cloned_activity = copy.copy(activity)
64+
if index < len(responses):
65+
cloned_activity.id = responses[index].id
66+
67+
# For certain channels, a ResourceResponse with an id is not always sent to the bot.
68+
# This fix uses the timestamp on the activity to populate its id for logging the transcript
69+
# If there is no outgoing timestamp, the current time for the bot is used for the activity.id
70+
if not cloned_activity.id:
71+
alphanumeric = string.ascii_lowercase + string.digits
72+
prefix = "g_" + "".join(
73+
random.choice(alphanumeric) for i in range(5)
74+
)
75+
epoch = datetime.datetime.utcfromtimestamp(0)
76+
if cloned_activity.timestamp:
77+
reference = cloned_activity.timestamp
78+
else:
79+
reference = datetime.datetime.today()
80+
delta = (reference - epoch).total_seconds() * 1000
81+
cloned_activity.id = f"{prefix}{delta}"
82+
self.log_activity(transcript, cloned_activity)
6283
return responses
6384

6485
context.on_send_activities(send_activities_handler)

libraries/botbuilder-core/botbuilder/core/turn_context.py

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
# Licensed under the MIT License.
33

44
import re
5-
from copy import copy
5+
from copy import copy, deepcopy
66
from datetime import datetime
77
from typing import List, Callable, Union, Dict
88
from botbuilder.schema import (
99
Activity,
1010
ActivityTypes,
1111
ConversationReference,
12+
InputHints,
1213
Mention,
1314
ResourceResponse,
1415
)
@@ -144,35 +145,57 @@ def set(self, key: str, value: object) -> None:
144145
self._services[key] = value
145146

146147
async def send_activity(
147-
self, *activity_or_text: Union[Activity, str]
148+
self,
149+
activity_or_text: Union[Activity, str],
150+
speak: str = None,
151+
input_hint: str = None,
148152
) -> ResourceResponse:
149153
"""
150154
Sends a single activity or message to the user.
151155
:param activity_or_text:
152156
:return:
153157
"""
154-
reference = TurnContext.get_conversation_reference(self.activity)
158+
if isinstance(activity_or_text, str):
159+
activity_or_text = Activity(
160+
text=activity_or_text,
161+
input_hint=input_hint or InputHints.accepting_input,
162+
speak=speak,
163+
)
164+
165+
result = await self.send_activities([activity_or_text])
166+
return result[0] if result else None
167+
168+
async def send_activities(
169+
self, activities: List[Activity]
170+
) -> List[ResourceResponse]:
171+
sent_non_trace_activity = False
172+
ref = TurnContext.get_conversation_reference(self.activity)
173+
174+
def activity_validator(activity: Activity) -> Activity:
175+
if not getattr(activity, "type", None):
176+
activity.type = ActivityTypes.message
177+
if activity.type != ActivityTypes.trace:
178+
nonlocal sent_non_trace_activity
179+
sent_non_trace_activity = True
180+
if not activity.input_hint:
181+
activity.input_hint = "acceptingInput"
182+
activity.id = None
183+
return activity
155184

156185
output = [
157-
TurnContext.apply_conversation_reference(
158-
Activity(text=a, type="message") if isinstance(a, str) else a, reference
186+
activity_validator(
187+
TurnContext.apply_conversation_reference(deepcopy(act), ref)
159188
)
160-
for a in activity_or_text
189+
for act in activities
161190
]
162-
for activity in output:
163-
if not activity.input_hint:
164-
activity.input_hint = "acceptingInput"
165191

166-
async def callback(context: "TurnContext", output):
167-
responses = await context.adapter.send_activities(context, output)
168-
context._responded = True # pylint: disable=protected-access
192+
async def logic():
193+
responses = await self.adapter.send_activities(self, output)
194+
if sent_non_trace_activity:
195+
self.responded = True
169196
return responses
170197

171-
result = await self._emit(
172-
self._on_send_activities, output, callback(self, output)
173-
)
174-
175-
return result[0] if result else ResourceResponse()
198+
return await self._emit(self._on_send_activities, output, logic())
176199

177200
async def update_activity(self, activity: Activity):
178201
"""

libraries/botbuilder-core/tests/simple_adapter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ async def delete_activity(
2424
if self._call_on_delete is not None:
2525
self._call_on_delete(reference)
2626

27-
async def send_activities(self, context: TurnContext, activities: List[Activity]):
27+
async def send_activities(
28+
self, context: TurnContext, activities: List[Activity]
29+
) -> List[ResourceResponse]:
2830
self.test_aux.assertIsNotNone(
2931
activities, "SimpleAdapter.delete_activity: missing reference"
3032
)

libraries/botbuilder-core/tests/test_activity_handler.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
ChannelAccount,
99
ConversationReference,
1010
MessageReaction,
11+
ResourceResponse,
1112
)
1213

1314

@@ -66,7 +67,9 @@ async def delete_activity(
6667
):
6768
raise NotImplementedError()
6869

69-
async def send_activities(self, context: TurnContext, activities: List[Activity]):
70+
async def send_activities(
71+
self, context: TurnContext, activities: List[Activity]
72+
) -> List[ResourceResponse]:
7073
raise NotImplementedError()
7174

7275
async def update_activity(self, context: TurnContext, activity: Activity):

libraries/botbuilder-core/tests/test_bot_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def validate_responses( # pylint: disable=unused-argument
4242

4343
resource_response = await context.send_activity(activity)
4444
self.assertTrue(
45-
resource_response.id == activity_id, "Incorrect response Id returned"
45+
resource_response.id != activity_id, "Incorrect response Id returned"
4646
)
4747

4848
async def test_continue_conversation_direct_msg(self):

libraries/botbuilder-core/tests/test_turn_context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828

2929
class SimpleAdapter(BotAdapter):
30-
async def send_activities(self, context, activities):
30+
async def send_activities(self, context, activities) -> List[ResourceResponse]:
3131
responses = []
3232
assert context is not None
3333
assert activities is not None
@@ -205,7 +205,7 @@ async def send_handler(context, activities, next_handler_coroutine):
205205
called = True
206206
assert activities is not None
207207
assert context is not None
208-
assert activities[0].id == "1234"
208+
assert not activities[0].id
209209
await next_handler_coroutine()
210210

211211
context.on_send_activities(send_handler)

samples/01.console-echo/adapter/console_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ async def process_activity(self, logic: Callable):
105105
context = TurnContext(self, activity)
106106
await self.run_pipeline(context, logic)
107107

108-
async def send_activities(self, context: TurnContext, activities: List[Activity]):
108+
async def send_activities(self, context: TurnContext, activities: List[Activity]) -> List[ResourceResponse]:
109109
"""
110110
Logs a series of activities to the console.
111111
:param context:

0 commit comments

Comments
 (0)
0