Skip to content

Commit d28ea30

Browse files
CopilotJimDaly
andcommitted
Add comprehensive Google-style docstrings to notifications library
Co-authored-by: JimDaly <[email protected]>
1 parent 5953445 commit d28ea30

File tree

11 files changed

+511
-33
lines changed

11 files changed

+511
-33
lines changed

libraries/microsoft-agents-a365-notifications/microsoft_agents_a365/notifications/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@
99

1010
# Main notification handler class
1111
from .agent_notification import (
12-
AgentNotification,
1312
AgentHandler,
13+
AgentNotification,
1414
)
1515

1616
# Import all models from the models subpackage
1717
from .models import (
18+
AgentLifecycleEvent,
1819
AgentNotificationActivity,
20+
AgentSubChannel,
1921
EmailReference,
20-
WpxComment,
2122
EmailResponse,
2223
NotificationTypes,
23-
AgentSubChannel,
24-
AgentLifecycleEvent,
24+
WpxComment,
2525
)
2626

2727
__all__ = [

libraries/microsoft-agents-a365-notifications/microsoft_agents_a365/notifications/agent_notification.py

Lines changed: 273 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
13
from __future__ import annotations
24

35
from collections.abc import Awaitable, Callable, Iterable
@@ -6,17 +8,69 @@
68
from microsoft_agents.activity import ChannelId
79
from microsoft_agents.hosting.core import TurnContext
810
from microsoft_agents.hosting.core.app.state import TurnState
11+
12+
from .models.agent_lifecycle_event import AgentLifecycleEvent
913
from .models.agent_notification_activity import AgentNotificationActivity, NotificationTypes
1014
from .models.agent_subchannel import AgentSubChannel
11-
from .models.agent_lifecycle_event import AgentLifecycleEvent
1215

1316
TContext = TypeVar("TContext", bound=TurnContext)
1417
TState = TypeVar("TState", bound=TurnState)
1518

19+
#: Type alias for agent notification handler functions.
20+
#:
21+
#: Agent handlers are async functions that process notifications from Microsoft 365
22+
#: applications. They receive the turn context, application state, and a typed
23+
#: notification activity wrapper.
24+
#:
25+
#: Args:
26+
#: context: The turn context for the current conversation turn.
27+
#: state: The application state for the current turn.
28+
#: notification: The typed notification activity with parsed entities.
29+
#:
30+
#: Example:
31+
#: ```python
32+
#: async def handle_email(
33+
#: context: TurnContext,
34+
#: state: TurnState,
35+
#: notification: AgentNotificationActivity
36+
#: ) -> None:
37+
#: email = notification.email
38+
#: if email:
39+
#: print(f"Processing email: {email.id}")
40+
#: ```
1641
AgentHandler = Callable[[TContext, TState, AgentNotificationActivity], Awaitable[None]]
1742

1843

1944
class AgentNotification:
45+
"""Handler for agent notifications from Microsoft 365 applications.
46+
47+
This class provides decorators for registering handlers that respond to notifications
48+
from various Microsoft 365 channels and subchannels. It supports routing based on
49+
channel ID, subchannel, and lifecycle events.
50+
51+
Args:
52+
app: The application instance that will handle the routed notifications.
53+
known_subchannels: Optional iterable of recognized subchannels. If None,
54+
defaults to all values in the AgentSubChannel enum.
55+
known_lifecycle_events: Optional iterable of recognized lifecycle events. If None,
56+
defaults to all values in the AgentLifecycleEvent enum.
57+
58+
Example:
59+
```python
60+
from microsoft_agents.hosting import Application
61+
from microsoft_agents_a365.notifications import AgentNotification
62+
63+
app = Application()
64+
notifications = AgentNotification(app)
65+
66+
@notifications.on_email()
67+
async def handle_email(context, state, notification):
68+
email = notification.email
69+
if email:
70+
await context.send_activity(f"Received email: {email.id}")
71+
```
72+
"""
73+
2074
def __init__(
2175
self,
2276
app: Any,
@@ -56,6 +110,31 @@ def on_agent_notification(
56110
channel_id: ChannelId,
57111
**kwargs: Any,
58112
):
113+
"""Register a handler for notifications from a specific channel and subchannel.
114+
115+
This decorator registers a handler function to be called when a notification is
116+
received from the specified channel and optional subchannel. The handler will
117+
receive a typed AgentNotificationActivity wrapper.
118+
119+
Args:
120+
channel_id: The channel ID specifying the channel and optional subchannel
121+
to listen for. Use "*" as the subchannel to match all subchannels.
122+
**kwargs: Additional keyword arguments passed to the app's add_route method.
123+
124+
Returns:
125+
A decorator function that registers the handler with the application.
126+
127+
Example:
128+
```python
129+
from microsoft_agents.activity import ChannelId
130+
131+
@notifications.on_agent_notification(
132+
ChannelId(channel="agents", sub_channel="email")
133+
)
134+
async def handle_custom_channel(context, state, notification):
135+
print(f"Received notification on {notification.channel}/{notification.sub_channel}")
136+
```
137+
"""
59138
registered_channel = channel_id.channel.lower()
60139
registered_subchannel = (channel_id.sub_channel or "*").lower()
61140

@@ -90,6 +169,26 @@ def on_agent_lifecycle_notification(
90169
lifecycle_event: str,
91170
**kwargs: Any,
92171
):
172+
"""Register a handler for agent lifecycle event notifications.
173+
174+
This decorator registers a handler function to be called when lifecycle events
175+
occur, such as user creation, deletion, or workload onboarding updates.
176+
177+
Args:
178+
lifecycle_event: The lifecycle event to listen for. Use "*" to match all
179+
lifecycle events, or specify a specific event from AgentLifecycleEvent.
180+
**kwargs: Additional keyword arguments passed to the app's add_route method.
181+
182+
Returns:
183+
A decorator function that registers the handler with the application.
184+
185+
Example:
186+
```python
187+
@notifications.on_agent_lifecycle_notification("agenticuseridentitycreated")
188+
async def handle_user_created(context, state, notification):
189+
print("New user created")
190+
```
191+
"""
93192
def route_selector(context: TurnContext) -> bool:
94193
ch = context.activity.channel_id
95194
received_channel = ch.channel if ch else ""
@@ -121,62 +220,235 @@ def decorator(handler: AgentHandler):
121220
def on_email(
122221
self, **kwargs: Any
123222
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
223+
"""Register a handler for Outlook email notifications.
224+
225+
This is a convenience decorator that registers a handler for notifications
226+
from the email subchannel.
227+
228+
Args:
229+
**kwargs: Additional keyword arguments passed to the app's add_route method.
230+
231+
Returns:
232+
A decorator function that registers the handler with the application.
233+
234+
Example:
235+
```python
236+
@notifications.on_email()
237+
async def handle_email(context, state, notification):
238+
email = notification.email
239+
if email:
240+
print(f"Received email: {email.id}")
241+
# Send a response
242+
response = EmailResponse.create_email_response_activity(
243+
"<p>Thank you for your email.</p>"
244+
)
245+
await context.send_activity(response)
246+
```
247+
"""
124248
return self.on_agent_notification(
125249
ChannelId(channel="agents", sub_channel=AgentSubChannel.EMAIL), **kwargs
126250
)
127251

128252
def on_word(
129253
self, **kwargs: Any
130254
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
255+
"""Register a handler for Microsoft Word comment notifications.
256+
257+
This is a convenience decorator that registers a handler for notifications
258+
from the Word subchannel.
259+
260+
Args:
261+
**kwargs: Additional keyword arguments passed to the app's add_route method.
262+
263+
Returns:
264+
A decorator function that registers the handler with the application.
265+
266+
Example:
267+
```python
268+
@notifications.on_word()
269+
async def handle_word_comment(context, state, notification):
270+
comment = notification.wpx_comment
271+
if comment:
272+
print(f"Received Word comment: {comment.comment_id}")
273+
```
274+
"""
131275
return self.on_agent_notification(
132276
ChannelId(channel="agents", sub_channel=AgentSubChannel.WORD), **kwargs
133277
)
134278

135279
def on_excel(
136280
self, **kwargs: Any
137281
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
282+
"""Register a handler for Microsoft Excel comment notifications.
283+
284+
This is a convenience decorator that registers a handler for notifications
285+
from the Excel subchannel.
286+
287+
Args:
288+
**kwargs: Additional keyword arguments passed to the app's add_route method.
289+
290+
Returns:
291+
A decorator function that registers the handler with the application.
292+
293+
Example:
294+
```python
295+
@notifications.on_excel()
296+
async def handle_excel_comment(context, state, notification):
297+
comment = notification.wpx_comment
298+
if comment:
299+
print(f"Received Excel comment: {comment.comment_id}")
300+
```
301+
"""
138302
return self.on_agent_notification(
139303
ChannelId(channel="agents", sub_channel=AgentSubChannel.EXCEL), **kwargs
140304
)
141305

142306
def on_powerpoint(
143307
self, **kwargs: Any
144308
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
309+
"""Register a handler for Microsoft PowerPoint comment notifications.
310+
311+
This is a convenience decorator that registers a handler for notifications
312+
from the PowerPoint subchannel.
313+
314+
Args:
315+
**kwargs: Additional keyword arguments passed to the app's add_route method.
316+
317+
Returns:
318+
A decorator function that registers the handler with the application.
319+
320+
Example:
321+
```python
322+
@notifications.on_powerpoint()
323+
async def handle_powerpoint_comment(context, state, notification):
324+
comment = notification.wpx_comment
325+
if comment:
326+
print(f"Received PowerPoint comment: {comment.comment_id}")
327+
```
328+
"""
145329
return self.on_agent_notification(
146330
ChannelId(channel="agents", sub_channel=AgentSubChannel.POWERPOINT), **kwargs
147331
)
148332

149333
def on_lifecycle(
150334
self, **kwargs: Any
151335
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
336+
"""Register a handler for all agent lifecycle event notifications.
337+
338+
This is a convenience decorator that registers a handler for all lifecycle
339+
events using the wildcard "*" matcher.
340+
341+
Args:
342+
**kwargs: Additional keyword arguments passed to the app's add_route method.
343+
344+
Returns:
345+
A decorator function that registers the handler with the application.
346+
347+
Example:
348+
```python
349+
@notifications.on_lifecycle()
350+
async def handle_any_lifecycle_event(context, state, notification):
351+
print(f"Lifecycle event type: {notification.notification_type}")
352+
```
353+
"""
152354
return self.on_lifecycle_notification("*", **kwargs)
153355

154356
def on_user_created(
155357
self, **kwargs: Any
156358
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
359+
"""Register a handler for user creation lifecycle events.
360+
361+
This is a convenience decorator that registers a handler specifically for
362+
agentic user identity creation events.
363+
364+
Args:
365+
**kwargs: Additional keyword arguments passed to the app's add_route method.
366+
367+
Returns:
368+
A decorator function that registers the handler with the application.
369+
370+
Example:
371+
```python
372+
@notifications.on_user_created()
373+
async def handle_user_created(context, state, notification):
374+
print("New agentic user identity created")
375+
```
376+
"""
157377
return self.on_lifecycle_notification(AgentLifecycleEvent.USERCREATED, **kwargs)
158378

159379
def on_user_workload_onboarding(
160380
self, **kwargs: Any
161381
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
382+
"""Register a handler for user workload onboarding update events.
383+
384+
This is a convenience decorator that registers a handler for events that occur
385+
when a user's workload onboarding status is updated.
386+
387+
Args:
388+
**kwargs: Additional keyword arguments passed to the app's add_route method.
389+
390+
Returns:
391+
A decorator function that registers the handler with the application.
392+
393+
Example:
394+
```python
395+
@notifications.on_user_workload_onboarding()
396+
async def handle_onboarding_update(context, state, notification):
397+
print("User workload onboarding status updated")
398+
```
399+
"""
162400
return self.on_lifecycle_notification(
163401
AgentLifecycleEvent.USERWORKLOADONBOARDINGUPDATED, **kwargs
164402
)
165403

166404
def on_user_deleted(
167405
self, **kwargs: Any
168406
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
407+
"""Register a handler for user deletion lifecycle events.
408+
409+
This is a convenience decorator that registers a handler specifically for
410+
agentic user identity deletion events.
411+
412+
Args:
413+
**kwargs: Additional keyword arguments passed to the app's add_route method.
414+
415+
Returns:
416+
A decorator function that registers the handler with the application.
417+
418+
Example:
419+
```python
420+
@notifications.on_user_deleted()
421+
async def handle_user_deleted(context, state, notification):
422+
print("Agentic user identity deleted")
423+
```
424+
"""
169425
return self.on_lifecycle_notification(AgentLifecycleEvent.USERDELETED, **kwargs)
170426

171427
@staticmethod
172428
def _normalize_subchannel(value: str | AgentSubChannel | None) -> str:
429+
"""Normalize a subchannel value to a lowercase string.
430+
431+
Args:
432+
value: The subchannel value to normalize, either as an enum or string.
433+
434+
Returns:
435+
The normalized lowercase subchannel string, or empty string if None.
436+
"""
173437
if value is None:
174438
return ""
175439
resolved = value.value if isinstance(value, AgentSubChannel) else str(value)
176440
return resolved.lower().strip()
177441

178442
@staticmethod
179443
def _normalize_lifecycleevent(value: str | AgentLifecycleEvent | None) -> str:
444+
"""Normalize a lifecycle event value to a lowercase string.
445+
446+
Args:
447+
value: The lifecycle event value to normalize, either as an enum or string.
448+
449+
Returns:
450+
The normalized lowercase lifecycle event string, or empty string if None.
451+
"""
180452
if value is None:
181453
return ""
182454
resolved = value.value if isinstance(value, AgentLifecycleEvent) else str(value)

0 commit comments

Comments
 (0)