microsoft-teams-apps 0.0.1a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- microsoft_teams_apps-0.0.1a1/.gitignore +34 -0
- microsoft_teams_apps-0.0.1a1/PKG-INFO +24 -0
- microsoft_teams_apps-0.0.1a1/README.md +7 -0
- microsoft_teams_apps-0.0.1a1/pyproject.toml +41 -0
- microsoft_teams_apps-0.0.1a1/scripts/generate_handlers.py +203 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/__init__.py +20 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/app.py +443 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/app_events.py +54 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/app_oauth.py +137 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/app_plugins.py +149 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/app_process.py +237 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/app_tokens.py +17 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/auth/__init__.py +12 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/auth/jwt_middleware.py +69 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/auth/service_token_validator.py +102 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/container.py +10 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/events/__init__.py +30 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/events/base.py +14 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/events/registry.py +119 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/events/types.py +107 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/http_plugin.py +323 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/options.py +31 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/__init__.py +32 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/metadata.py +131 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/plugin_activity_event.py +27 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/plugin_activity_response_event.py +27 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/plugin_activity_sent_event.py +25 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/plugin_base.py +54 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/plugin_error_event.py +24 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/plugin_start_event.py +13 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/sender.py +26 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/plugins/streamer.py +53 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/routing/__init__.py +10 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/routing/activity_context.py +243 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/routing/activity_handlers.py +121 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/routing/activity_route_configs.py +565 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/routing/generated_handlers.py +3056 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/routing/router.py +33 -0
- microsoft_teams_apps-0.0.1a1/src/microsoft/teams/apps/routing/type_validation.py +67 -0
- microsoft_teams_apps-0.0.1a1/tests/conftest.py +59 -0
- microsoft_teams_apps-0.0.1a1/tests/test_app.py +485 -0
- microsoft_teams_apps-0.0.1a1/tests/test_app_oauth.py +350 -0
- microsoft_teams_apps-0.0.1a1/tests/test_http_plugin.py +290 -0
- microsoft_teams_apps-0.0.1a1/tests/test_plugin_decorator.py +36 -0
- microsoft_teams_apps-0.0.1a1/tests/test_service_token_validator.py +217 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
|
|
7
|
+
# Environments
|
|
8
|
+
.env
|
|
9
|
+
.venv
|
|
10
|
+
env/
|
|
11
|
+
venv/
|
|
12
|
+
ENV/
|
|
13
|
+
env.bak/
|
|
14
|
+
venv.bak/
|
|
15
|
+
|
|
16
|
+
# mypy
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.dmypy.json
|
|
19
|
+
dmypy.json
|
|
20
|
+
|
|
21
|
+
.copilot-instructions.md
|
|
22
|
+
|
|
23
|
+
# other
|
|
24
|
+
.DS_STORE
|
|
25
|
+
*.bak
|
|
26
|
+
*~
|
|
27
|
+
*.tmp
|
|
28
|
+
|
|
29
|
+
ref/
|
|
30
|
+
py.typed
|
|
31
|
+
CLAUDE.md
|
|
32
|
+
|
|
33
|
+
.env.claude/
|
|
34
|
+
.claude/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: microsoft-teams-apps
|
|
3
|
+
Version: 0.0.1a1
|
|
4
|
+
Summary: The app package for a Microsoft Teams agent
|
|
5
|
+
Author-email: Microsoft <TeamsAISDKFeedback@microsoft.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: agents,ai,bot,microsoft,teams
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Requires-Dist: cryptography>=3.4.0
|
|
10
|
+
Requires-Dist: dependency-injector>=4.48.1
|
|
11
|
+
Requires-Dist: fastapi>=0.115.13
|
|
12
|
+
Requires-Dist: microsoft-teams-api
|
|
13
|
+
Requires-Dist: microsoft-teams-common
|
|
14
|
+
Requires-Dist: pyjwt[crypto]>=2.10.0
|
|
15
|
+
Requires-Dist: uvicorn>=0.34.3
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
> [!CAUTION]
|
|
19
|
+
> This project is in active development and not ready for production use. It has not been publicly announced yet.
|
|
20
|
+
|
|
21
|
+
# Microsoft Teams App Framework
|
|
22
|
+
|
|
23
|
+
High-level framework for building Microsoft Teams bots and applications.
|
|
24
|
+
Handles routing, middleware, events, and provides OAuth integration for Teams apps.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
> [!CAUTION]
|
|
2
|
+
> This project is in active development and not ready for production use. It has not been publicly announced yet.
|
|
3
|
+
|
|
4
|
+
# Microsoft Teams App Framework
|
|
5
|
+
|
|
6
|
+
High-level framework for building Microsoft Teams bots and applications.
|
|
7
|
+
Handles routing, middleware, events, and provides OAuth integration for Teams apps.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "microsoft-teams-apps"
|
|
3
|
+
version = "0.0.1-alpha.1"
|
|
4
|
+
description = "The app package for a Microsoft Teams agent"
|
|
5
|
+
authors = [{ name = "Microsoft", email = "TeamsAISDKFeedback@microsoft.com" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
repository = "https://github.com/microsoft/teams.py"
|
|
9
|
+
keywords = ["microsoft", "teams", "ai", "bot", "agents"]
|
|
10
|
+
license = "MIT"
|
|
11
|
+
|
|
12
|
+
dependencies = [
|
|
13
|
+
"fastapi>=0.115.13",
|
|
14
|
+
"microsoft-teams-api",
|
|
15
|
+
"microsoft-teams-common",
|
|
16
|
+
"uvicorn>=0.34.3",
|
|
17
|
+
"cryptography>=3.4.0",
|
|
18
|
+
"pyjwt[crypto]>=2.10.0",
|
|
19
|
+
"dependency-injector>=4.48.1",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
test = [
|
|
24
|
+
"pytest>=8.0.0",
|
|
25
|
+
"pytest-asyncio>=0.24.0",
|
|
26
|
+
"httpx>=0.27.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["hatchling"]
|
|
31
|
+
build-backend = "hatchling.build"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/microsoft", "scripts"]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
generate-activity-handlers = "scripts.generate_handlers:generate_activity_handlers"
|
|
38
|
+
|
|
39
|
+
[tool.uv.sources]
|
|
40
|
+
microsoft-teams-common = { workspace = true }
|
|
41
|
+
microsoft-teams-api = { workspace = true }
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
Licensed under the MIT License.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import importlib.util
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import microsoft.teams.apps.routing.activity_route_configs as activity_config
|
|
13
|
+
|
|
14
|
+
# Import the activity config directly without going through the package hierarchy
|
|
15
|
+
activity_config_path = (
|
|
16
|
+
Path(__file__).parent.parent / "src" / "microsoft" / "teams" / "app" / "routing" / "activity_route_configs.py"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Load the activity config module directly because we don't want to have a dependency on the package
|
|
20
|
+
# as it will lead to a circular dependency
|
|
21
|
+
# https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path
|
|
22
|
+
if not TYPE_CHECKING:
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
# Add the src directory to sys.path so relative imports work
|
|
26
|
+
src_path = str(Path(__file__).parent.parent / "src")
|
|
27
|
+
if src_path not in sys.path:
|
|
28
|
+
sys.path.insert(0, src_path)
|
|
29
|
+
|
|
30
|
+
spec = importlib.util.spec_from_file_location(
|
|
31
|
+
"microsoft.teams.apps.routing.activity_route_configs", activity_config_path
|
|
32
|
+
)
|
|
33
|
+
assert spec is not None, f"Could not find activity_route_configs.py at {activity_config_path}"
|
|
34
|
+
activity_config = importlib.util.module_from_spec(spec)
|
|
35
|
+
assert spec.loader is not None, f"Could not load activity_route_configs.py at {activity_config_path}"
|
|
36
|
+
spec.loader.exec_module(activity_config)
|
|
37
|
+
|
|
38
|
+
ACTIVITY_ROUTES = activity_config.ACTIVITY_ROUTES
|
|
39
|
+
ActivityConfig = activity_config.ActivityConfig
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def generate_imports() -> str:
|
|
43
|
+
"""Generate import statements for the generated file."""
|
|
44
|
+
imports = {
|
|
45
|
+
"from abc import ABC, abstractmethod",
|
|
46
|
+
"from typing import Callable, Optional, Union, Awaitable, overload",
|
|
47
|
+
"from .activity_context import ActivityContext",
|
|
48
|
+
"from .router import ActivityRouter",
|
|
49
|
+
"from .type_validation import validate_handler_type",
|
|
50
|
+
"from .activity_route_configs import ACTIVITY_ROUTES",
|
|
51
|
+
"from microsoft.teams.models import ActivityBase",
|
|
52
|
+
"from logging import Logger",
|
|
53
|
+
"from microsoft.teams.api import InvokeResponse",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Add imports for each activity class
|
|
57
|
+
for config in ACTIVITY_ROUTES.values():
|
|
58
|
+
# Use explicit input_type_name if provided, otherwise fall back to __name__
|
|
59
|
+
class_name = config.input_model if isinstance(config.input_model, str) else config.input_model.__name__
|
|
60
|
+
if class_name == "ActivityBase":
|
|
61
|
+
imports.add(f"from microsoft.teams.api.models import {class_name}")
|
|
62
|
+
else:
|
|
63
|
+
imports.add(f"from microsoft.teams.api.activities import {class_name}")
|
|
64
|
+
if config.output_model or config.output_type_name:
|
|
65
|
+
# Use explicit output_type_name if provided, otherwise fall back to __name__
|
|
66
|
+
output_class_name = config.output_type_name
|
|
67
|
+
if not output_class_name:
|
|
68
|
+
if config.output_model:
|
|
69
|
+
output_class_name = config.output_model.__name__
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(f"Output type for {config.name} must be specified in the config or as a string.")
|
|
72
|
+
imports.add(f"from microsoft.teams.api.models.invoke_response import {output_class_name}")
|
|
73
|
+
|
|
74
|
+
return "\n".join(sorted(imports))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def generate_method(config: ActivityConfig, config_key: str) -> str:
|
|
78
|
+
"""Generate a single handler method with strict typing and runtime validation."""
|
|
79
|
+
method_name = config.method_name
|
|
80
|
+
activity_name = config.name
|
|
81
|
+
|
|
82
|
+
# Use the explicit input_type_name if provided, otherwise fall back to __name__
|
|
83
|
+
input_class_name = config.input_model if isinstance(config.input_model, str) else config.input_model.__name__
|
|
84
|
+
|
|
85
|
+
# Determine output type
|
|
86
|
+
if config.output_type_name or config.output_model:
|
|
87
|
+
# Use explicit output_type_name if provided, otherwise fall back to __name__
|
|
88
|
+
output_class_name = config.output_type_name
|
|
89
|
+
if not output_class_name:
|
|
90
|
+
if config.output_model:
|
|
91
|
+
output_class_name = config.output_model.__name__
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(f"Output type for {method_name} must be specified in the config or as a string.")
|
|
94
|
+
if config.is_invoke:
|
|
95
|
+
output_class_name = f"Union[InvokeResponse[{output_class_name}], {output_class_name}]"
|
|
96
|
+
output_type = f"Awaitable[{output_class_name}]"
|
|
97
|
+
else:
|
|
98
|
+
if config.is_invoke:
|
|
99
|
+
output_type = "Awaitable[Union[InvokeResponse[None], None]]"
|
|
100
|
+
else:
|
|
101
|
+
output_type = "Awaitable[None]"
|
|
102
|
+
|
|
103
|
+
returnTypeStr = f"Callable[[ActivityContext[{input_class_name}]], {output_type}]"
|
|
104
|
+
returnTypeDecoratorStr = f"Callable[[{returnTypeStr}], {returnTypeStr}]"
|
|
105
|
+
|
|
106
|
+
return f''' @overload
|
|
107
|
+
def {method_name}(self, handler: {returnTypeStr}) -> {returnTypeStr}: ...
|
|
108
|
+
|
|
109
|
+
@overload
|
|
110
|
+
def {method_name}(self) -> {returnTypeDecoratorStr}: ...
|
|
111
|
+
|
|
112
|
+
def {method_name}(self, handler: Optional[{returnTypeStr}] = None) -> {returnTypeDecoratorStr} | {returnTypeStr}:
|
|
113
|
+
"""Register a {activity_name} activity handler."""
|
|
114
|
+
def decorator(func: {returnTypeStr}) -> {returnTypeStr}:
|
|
115
|
+
validate_handler_type(self.logger, func, {input_class_name}, "{method_name}", "{input_class_name}")
|
|
116
|
+
config = ACTIVITY_ROUTES["{config_key}"]
|
|
117
|
+
self.router.add_handler(config.selector, func)
|
|
118
|
+
return func
|
|
119
|
+
|
|
120
|
+
if handler is not None:
|
|
121
|
+
return decorator(handler)
|
|
122
|
+
return decorator''' # noqa: E501, W291, W293, W391
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def generate_mixin_class() -> str:
|
|
126
|
+
"""Generate the complete ActivityHandlerMixin class."""
|
|
127
|
+
methods: list[str] = []
|
|
128
|
+
|
|
129
|
+
for config_key, config in ACTIVITY_ROUTES.items():
|
|
130
|
+
methods.append(generate_method(config, config_key))
|
|
131
|
+
|
|
132
|
+
methods_code = "\n\n".join(methods)
|
|
133
|
+
|
|
134
|
+
return f'''class GeneratedActivityHandlerMixin(ABC):
|
|
135
|
+
"""Mixin class providing typed activity handler registration methods."""
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def router(self) -> ActivityRouter:
|
|
140
|
+
"""The activity router instance. Must be implemented by the concrete class."""
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def logger(self) -> Logger:
|
|
146
|
+
"""The logger instance used by the app."""
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
{methods_code}'''
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def generate_file_header() -> str:
|
|
153
|
+
"""Generate the file header with copyright and description."""
|
|
154
|
+
return '''"""
|
|
155
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
156
|
+
Licensed under the MIT License.
|
|
157
|
+
|
|
158
|
+
GENERATED FILE - DO NOT EDIT MANUALLY
|
|
159
|
+
This file is generated by generate_handlers.py based on activity_config.py
|
|
160
|
+
|
|
161
|
+
To regenerate, run:
|
|
162
|
+
uv run generate-activity-handlers
|
|
163
|
+
"""'''
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def generate_activity_handlers():
|
|
167
|
+
"""Generate the complete activity handlers file."""
|
|
168
|
+
print("🔧 Generating activity handlers...")
|
|
169
|
+
|
|
170
|
+
# Build the complete file content
|
|
171
|
+
content_parts = [
|
|
172
|
+
generate_file_header(),
|
|
173
|
+
"",
|
|
174
|
+
generate_imports(),
|
|
175
|
+
"",
|
|
176
|
+
"",
|
|
177
|
+
generate_mixin_class(),
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
generated_code = "\n".join(content_parts)
|
|
181
|
+
|
|
182
|
+
# Write to the message_handler directory in the source code
|
|
183
|
+
# Use Path(__file__) to find this script's location, then navigate to the target
|
|
184
|
+
script_dir = Path(__file__).parent
|
|
185
|
+
source_dir = script_dir.parent / "src" / "microsoft" / "teams" / "app" / "routing"
|
|
186
|
+
output_path = source_dir / "generated_handlers.py"
|
|
187
|
+
|
|
188
|
+
# Ensure the target directory exists
|
|
189
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
output_path.write_text(generated_code)
|
|
191
|
+
|
|
192
|
+
print(f"✅ Generated {len(ACTIVITY_ROUTES)} activity handlers in {output_path}")
|
|
193
|
+
print("📝 Generated methods:")
|
|
194
|
+
for config in ACTIVITY_ROUTES.values():
|
|
195
|
+
print(f" - {config.method_name}() for {config.name} activities")
|
|
196
|
+
|
|
197
|
+
# execute poe fmt on the generated file
|
|
198
|
+
subprocess.run(["poe", "lint", "--select", "I", "--select", "F401", "--fix"], check=True)
|
|
199
|
+
subprocess.run(["poe", "fmt", str(output_path)], check=True)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
generate_activity_handlers()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
Licensed under the MIT License.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from . import auth, events, plugins
|
|
7
|
+
from .app import App
|
|
8
|
+
from .app_tokens import AppTokens
|
|
9
|
+
from .auth import * # noqa: F403
|
|
10
|
+
from .events import * # noqa: F401, F403
|
|
11
|
+
from .http_plugin import HttpPlugin
|
|
12
|
+
from .options import AppOptions
|
|
13
|
+
from .plugins import * # noqa: F401, F403
|
|
14
|
+
from .routing import ActivityContext
|
|
15
|
+
|
|
16
|
+
# Combine all exports from submodules
|
|
17
|
+
__all__: list[str] = ["App", "AppOptions", "HttpPlugin", "ActivityContext", "AppTokens"]
|
|
18
|
+
__all__.extend(auth.__all__)
|
|
19
|
+
__all__.extend(events.__all__)
|
|
20
|
+
__all__.extend(plugins.__all__)
|