Skip to content

Commit 92f7058

Browse files
authored
Axsuarez/skills layer (#492)
* Skill layer working, oauth prompt and testing pending * pylint: Skill layer working, oauth prompt and testing pending * Updating minor skills PRs to match C# * Removing accidental changes in samples 1. and 13. * Adding custom exception for channel service handler * Skills error handler * Skills error handler * pylint: Solved conflicts w/master * pylint: Solved conflicts w/master
1 parent e69b171 commit 92f7058

35 files changed

+904
-40
lines changed

.pylintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ extension-pkg-whitelist=
77

88
# Add files or directories to the blacklist. They should be base names, not
99
# paths.
10-
ignore=CVS,build,botbuilder-schema,samples,django_tests,Generator,operations,operations_async
10+
ignore=CVS,build,botbuilder-schema,samples,django_tests,Generator,operations,operations_async,schema
1111

1212
# Add files or directories matching the regex patterns to the blacklist. The
1313
# regex matches against base names, not paths.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .about import __version__
1010
from .activity_handler import ActivityHandler
1111
from .auto_save_state_middleware import AutoSaveStateMiddleware
12+
from .bot import Bot
1213
from .bot_assert import BotAssert
1314
from .bot_adapter import BotAdapter
1415
from .bot_framework_adapter import BotFrameworkAdapter, BotFrameworkAdapterSettings
@@ -42,6 +43,7 @@
4243
"ActivityHandler",
4344
"AnonymousReceiveMiddleware",
4445
"AutoSaveStateMiddleware",
46+
"Bot",
4547
"BotAdapter",
4648
"BotAssert",
4749
"BotFrameworkAdapter",

libraries/botbuilder-core/botbuilder/core/activity_handler.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ async def on_turn(self, turn_context: TurnContext):
3232
await self.on_message_reaction_activity(turn_context)
3333
elif turn_context.activity.type == ActivityTypes.event:
3434
await self.on_event_activity(turn_context)
35+
elif turn_context.activity.type == ActivityTypes.end_of_conversation:
36+
await self.on_end_of_conversation(turn_context)
3537
else:
3638
await self.on_unrecognized_activity_type(turn_context)
3739

@@ -58,12 +60,12 @@ async def on_conversation_update_activity(self, turn_context: TurnContext):
5860
return
5961

6062
async def on_members_added_activity(
61-
self, members_added: ChannelAccount, turn_context: TurnContext
63+
self, members_added: List[ChannelAccount], turn_context: TurnContext
6264
): # pylint: disable=unused-argument
6365
return
6466

6567
async def on_members_removed_activity(
66-
self, members_removed: ChannelAccount, turn_context: TurnContext
68+
self, members_removed: List[ChannelAccount], turn_context: TurnContext
6769
): # pylint: disable=unused-argument
6870
return
6971

@@ -104,6 +106,11 @@ async def on_event( # pylint: disable=unused-argument
104106
):
105107
return
106108

109+
async def on_end_of_conversation( # pylint: disable=unused-argument
110+
self, turn_context: TurnContext
111+
):
112+
return
113+
107114
async def on_unrecognized_activity_type( # pylint: disable=unused-argument
108115
self, turn_context: TurnContext
109116
):

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
ResourceResponse,
2121
TokenResponse,
2222
)
23+
from botframework.connector.auth import ClaimsIdentity
2324
from ..bot_adapter import BotAdapter
2425
from ..turn_context import TurnContext
2526
from ..user_token_provider import UserTokenProvider
@@ -157,16 +158,23 @@ async def update_activity(self, context, activity: Activity):
157158
self.updated_activities.append(activity)
158159

159160
async def continue_conversation(
160-
self, bot_id: str, reference: ConversationReference, callback: Callable
161+
self,
162+
reference: ConversationReference,
163+
callback: Callable,
164+
bot_id: str = None,
165+
claims_identity: ClaimsIdentity = None, # pylint: disable=unused-argument
161166
):
162167
"""
163168
The `TestAdapter` just calls parent implementation.
164-
:param bot_id
165169
:param reference:
166170
:param callback:
171+
:param bot_id:
172+
:param claims_identity:
167173
:return:
168174
"""
169-
await super().continue_conversation(bot_id, reference, callback)
175+
await super().continue_conversation(
176+
reference, callback, bot_id, claims_identity
177+
)
170178

171179
async def receive_activity(self, activity):
172180
"""
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from abc import ABC, abstractmethod
5+
6+
from .turn_context import TurnContext
7+
8+
9+
class Bot(ABC):
10+
"""
11+
Represents a bot that can operate on incoming activities.
12+
"""
13+
14+
@abstractmethod
15+
async def on_turn(self, context: TurnContext):
16+
"""
17+
When implemented in a bot, handles an incoming activity.
18+
:param context: The context object for this turn.
19+
:return:
20+
"""
21+
raise NotImplementedError()

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from abc import ABC, abstractmethod
55
from typing import List, Callable, Awaitable
66
from botbuilder.schema import Activity, ConversationReference, ResourceResponse
7+
from botframework.connector.auth import ClaimsIdentity
78

89
from . import conversation_reference_extension
910
from .bot_assert import BotAssert
@@ -62,8 +63,12 @@ def use(self, middleware):
6263
return self
6364

6465
async def continue_conversation(
65-
self, bot_id: str, reference: ConversationReference, callback: Callable
66-
): # pylint: disable=unused-argument
66+
self,
67+
reference: ConversationReference,
68+
callback: Callable,
69+
bot_id: str = None, # pylint: disable=unused-argument
70+
claims_identity: ClaimsIdentity = None, # pylint: disable=unused-argument
71+
):
6772
"""
6873
Sends a proactive message to a conversation. Call this method to proactively send a message to a conversation.
6974
Most _channels require a user to initiate a conversation with a bot before the bot can send activities
@@ -73,6 +78,7 @@ async def continue_conversation(
7378
which is multi-tenant aware. </param>
7479
:param reference: A reference to the conversation to continue.</param>
7580
:param callback: The method to call for the resulting bot turn.</param>
81+
:param claims_identity:
7682
"""
7783
context = TurnContext(
7884
self, conversation_reference_extension.get_continuation_activity(reference)

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

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from .bot_adapter import BotAdapter
3838
from .turn_context import TurnContext
3939
from .user_token_provider import UserTokenProvider
40+
from .conversation_reference_extension import get_continuation_activity
4041

4142
USER_AGENT = f"Microsoft-BotFramework/3.1 (BotBuilder Python/{__version__})"
4243
OAUTH_ENDPOINT = "https://api.botframework.com"
@@ -128,8 +129,14 @@ def __init__(self, settings: BotFrameworkAdapterSettings):
128129
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
129130
)
130131

132+
self._connector_client_cache: Dict[str, ConnectorClient] = {}
133+
131134
async def continue_conversation(
132-
self, bot_id: str, reference: ConversationReference, callback: Callable
135+
self,
136+
reference: ConversationReference,
137+
callback: Callable,
138+
bot_id: str = None,
139+
claims_identity: ClaimsIdentity = None, # pylint: disable=unused-argument
133140
):
134141
"""
135142
Continues a conversation with a user. This is often referred to as the bots "Proactive Messaging"
@@ -139,18 +146,26 @@ async def continue_conversation(
139146
:param bot_id:
140147
:param reference:
141148
:param callback:
149+
:param claims_identity:
142150
:return:
143151
"""
144152

145153
# TODO: proactive messages
154+
if not claims_identity:
155+
if not bot_id:
156+
raise TypeError("Expected bot_id: str but got None instead")
157+
158+
claims_identity = ClaimsIdentity(
159+
claims={
160+
AuthenticationConstants.AUDIENCE_CLAIM: bot_id,
161+
AuthenticationConstants.APP_ID_CLAIM: bot_id,
162+
},
163+
is_authenticated=True,
164+
)
146165

147-
if not bot_id:
148-
raise TypeError("Expected bot_id: str but got None instead")
149-
150-
request = TurnContext.apply_conversation_reference(
151-
Activity(), reference, is_incoming=True
152-
)
153-
context = self.create_context(request)
166+
context = TurnContext(self, get_continuation_activity(reference))
167+
context.turn_state[BOT_IDENTITY_KEY] = claims_identity
168+
context.turn_state["BotCallbackHandler"] = callback
154169
return await self.run_pipeline(context, callback)
155170

156171
async def create_conversation(
@@ -660,8 +675,16 @@ async def create_connector_client(
660675
else:
661676
credentials = self._credentials
662677

663-
client = ConnectorClient(credentials, base_url=service_url)
664-
client.config.add_user_agent(USER_AGENT)
678+
client_key = (
679+
f"{service_url}{credentials.microsoft_app_id if credentials else ''}"
680+
)
681+
client = self._connector_client_cache.get(client_key)
682+
683+
if not client:
684+
client = ConnectorClient(credentials, base_url=service_url)
685+
client.config.add_user_agent(USER_AGENT)
686+
self._connector_client_cache[client_key] = client
687+
665688
return client
666689

667690
def create_token_api_client(self, service_url: str) -> TokenApiClient:

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

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

88
from .aiohttp_channel_service import aiohttp_channel_service_routes
99
from .bot_framework_http_client import BotFrameworkHttpClient
10-
from .channel_service_handler import ChannelServiceHandler
10+
from .channel_service_handler import BotActionNotImplementedError, ChannelServiceHandler
11+
from .aiohttp_channel_service_exception_middleware import aiohttp_error_middleware
1112

1213
__all__ = [
1314
"aiohttp_channel_service_routes",
1415
"BotFrameworkHttpClient",
16+
"BotActionNotImplementedError",
1517
"ChannelServiceHandler",
18+
"aiohttp_error_middleware",
1619
]

libraries/botbuilder-core/botbuilder/core/integration/aiohttp_channel_service.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from aiohttp.web import RouteTableDef, Request, Response
77
from msrest.serialization import Model
8+
89
from botbuilder.schema import (
910
Activity,
1011
AttachmentData,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from aiohttp.web import (
2+
middleware,
3+
HTTPNotImplemented,
4+
HTTPUnauthorized,
5+
HTTPNotFound,
6+
HTTPInternalServerError,
7+
)
8+
9+
from .channel_service_handler import BotActionNotImplementedError
10+
11+
12+
@middleware
13+
async def aiohttp_error_middleware(request, handler):
14+
try:
15+
response = await handler(request)
16+
return response
17+
except BotActionNotImplementedError:
18+
raise HTTPNotImplemented()
19+
except PermissionError:
20+
raise HTTPUnauthorized()
21+
except KeyError:
22+
raise HTTPNotFound()
23+
except Exception:
24+
raise HTTPInternalServerError()

0 commit comments

Comments
 (0)