Skip to content

Commit

Permalink
add and test messages invoke module
Browse files Browse the repository at this point in the history
  • Loading branch information
aacebo committed Jan 22, 2025
1 parent 88ff85b commit a3d8e93
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 1 deletion.
12 changes: 11 additions & 1 deletion python/packages/ai/teams/ai/citations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,19 @@
from .citations import (
AIEntity,
Appearance,
AppearanceImage,
ClientCitation,
ClientCitationIconName,
Pattern,
SensitivityUsageInfo,
)

__all__ = ["ClientCitation", "Appearance", "SensitivityUsageInfo", "Pattern", "AIEntity"]
__all__ = [
"ClientCitation",
"ClientCitationIconName",
"Appearance",
"AppearanceImage",
"SensitivityUsageInfo",
"Pattern",
"AIEntity",
]
10 changes: 10 additions & 0 deletions python/packages/ai/teams/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from .feedback_loop_data import FeedbackLoopData
from .meetings.meetings import Meetings
from .message_extensions.message_extensions import MessageExtensions
from .messages.messages import Messages
from .route import Route, RouteHandler
from .state import TurnState
from .task_modules import TaskModules
Expand Down Expand Up @@ -79,6 +80,7 @@ class Application(Bot, Generic[StateT]):
_message_extensions: MessageExtensions[StateT]
_task_modules: TaskModules[StateT]
_meetings: Meetings[StateT]
_messages: Messages[StateT]

def __init__(self, options: ApplicationOptions = ApplicationOptions()) -> None:
"""
Expand All @@ -96,6 +98,7 @@ def __init__(self, options: ApplicationOptions = ApplicationOptions()) -> None:
self._routes, options.task_modules.task_data_filter
)
self._meetings = Meetings[StateT](self._routes)
self._messages = Messages[StateT](self._routes)

if options.long_running_messages and (not options.adapter or not options.bot_app_id):
raise ApplicationError("""
Expand Down Expand Up @@ -189,6 +192,13 @@ def meetings(self) -> Meetings[StateT]:
"""
return self._meetings

@property
def messages(self) -> Messages[StateT]:
"""
Access the application's messages functionalities.
"""
return self._messages

def activity(
self, type: ActivityType
) -> Callable[[RouteHandler[StateT]], RouteHandler[StateT]]:
Expand Down
8 changes: 8 additions & 0 deletions python/packages/ai/teams/messages/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

from .messages import Messages

__all__ = ["Messages"]
67 changes: 67 additions & 0 deletions python/packages/ai/teams/messages/messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

from __future__ import annotations

from typing import Awaitable, Callable, Generic, List, TypeVar

from botbuilder.core import TurnContext
from botbuilder.core.serializer_helper import serializer_helper
from botbuilder.schema import ActivityTypes

from teams.route import Route
from teams.state import TurnState

FETCH_INVOKE_NAME = "message/fetchTask"

StateT = TypeVar("StateT", bound=TurnState)


class Messages(Generic[StateT]):
_routes: List[Route[StateT]] = []

def __init__(self, routes: List[Route[StateT]]) -> None:
self._routes = routes

def fetch(self) -> Callable[
[Callable[[TurnContext, StateT, dict], Awaitable[None]]],
Callable[[TurnContext, StateT, dict], Awaitable[None]],
]:
"""
Adds a route for handling the message fetch task activity.
This method can be used as either a decorator or a method.
```python
# Use this method as a decorator
@app.messages.fetch()
async def fetch(context: TurnContext, state: TurnState, data: dict):
print(f"Execute with data: {data}")
return True
# Pass a function to this method
app.messages.fetch()(fetch)
```
"""

# Create route selector for the handler
def __selector__(context: TurnContext) -> bool:
return (
context.activity.type == ActivityTypes.invoke
and context.activity.name == FETCH_INVOKE_NAME
)

def __call__(
func: Callable[[TurnContext, StateT, dict], Awaitable[None]],
) -> Callable[[TurnContext, StateT, dict], Awaitable[None]]:
async def __handler__(context: TurnContext, state: StateT):
if not context.activity.value:
return False
await func(context, state, context.activity.value)
return True

self._routes.append(Route[StateT](__selector__, __handler__))
return func

return __call__
47 changes: 47 additions & 0 deletions python/packages/ai/tests/test_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

from unittest import IsolatedAsyncioTestCase, mock

import pytest
from botbuilder.core import TurnContext
from botbuilder.schema import Activity, ChannelAccount, ConversationAccount

from teams import Application
from tests.utils import SimpleAdapter


class TestMessages(IsolatedAsyncioTestCase):
application: Application

@pytest.fixture(autouse=True)
def before_each(self):
self.application = Application()
yield

@pytest.mark.asyncio
async def test_fetch(self):
handler = mock.AsyncMock() # mock handler
self.application.messages.fetch()(handler)
self.assertEqual(len(self.application._routes), 1)

# The handler will be tirggered
await self.application.on_turn(
TurnContext(
SimpleAdapter(),
Activity(
id="1234",
type="invoke",
name="message/fetchTask",
value={"hello": "world"},
from_property=ChannelAccount(id="user", name="Task Modules Test User"),
recipient=ChannelAccount(id="bot", name="Task Modules Test Bot"),
conversation=ConversationAccount(id="convo", name="Task Modules Test Convo"),
channel_id="UnitTest",
locale="en-uS",
service_url="https://example.org",
),
)
)

0 comments on commit a3d8e93

Please sign in to comment.