Skip to content

Commit 94be2d6

Browse files
authored
Merge pull request #325 from microsoft/main
Merge main into alpha/v2.0.0
2 parents 8389ea8 + 68f2ea3 commit 94be2d6

136 files changed

Lines changed: 4973 additions & 2173 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,4 @@ jobs:
7979
- name: Run Tests
8080
run: |
8181
source .venv/bin/activate
82-
pytest packages
82+
uv run poe test-cov-ci

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ venv.bak/
1818
.dmypy.json
1919
dmypy.json
2020

21+
# pytest-cov
22+
.coverage
23+
.coverage.*
24+
htmlcov/
25+
coverage.xml
26+
2127
.copilot-instructions.md
2228

2329
# other
@@ -28,10 +34,10 @@ dmypy.json
2834

2935
ref/
3036
py.typed
31-
CLAUDE.md
3237

3338
.env.claude/
3439
.claude/
40+
tmpclaude-*-cwd
3541

3642
examples/**/.vscode/
3743
examples/**/appPackage/

CLAUDE.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Microsoft Teams Python SDK — a UV workspace with multiple packages providing APIs, common utilities, and integrations for Microsoft Teams.
8+
9+
## Development Setup
10+
11+
### Prerequisites
12+
- UV >= 0.8.11
13+
- Python >= 3.12
14+
15+
### Commands
16+
```bash
17+
uv sync # Install virtual env and dependencies
18+
source .venv/bin/activate # Activate virtual environment
19+
pre-commit install # Install pre-commit hooks
20+
21+
poe fmt # Format code with ruff
22+
poe lint # Lint code with ruff
23+
poe check # Run both format and lint
24+
poe test # Run tests with pytest
25+
pyright # Run type checker
26+
```
27+
28+
## Tooling
29+
30+
- **Formatter/Linter**: Ruff — line length 120, rules: E, F, W, B, Q, I, ASYNC
31+
- **Type checker**: Pyright
32+
- **Test framework**: pytest + pytest-asyncio (Ruff bans importing the unittest test framework; unittest.mock is allowed and used)
33+
34+
## Architecture
35+
36+
### Workspace Structure
37+
All packages live in `packages/`, each with `src/microsoft_teams/<package>/` layout:
38+
39+
| Package | Description |
40+
|---------|-------------|
41+
| `api` | Core API clients, models (Account, Activity, Conversation), auth |
42+
| `apps` | App orchestrator, plugins, routing, events, HttpServer |
43+
| `common` | HTTP client abstraction, logging, storage |
44+
| `cards` | Adaptive cards |
45+
| `ai` | AI/function calling utilities |
46+
| `botbuilder` | Bot Framework integration plugin |
47+
| `devtools` | Development tools plugin |
48+
| `mcpplugin` | MCP server plugin |
49+
| `a2aprotocol` | A2A protocol plugin |
50+
| `graph` | Microsoft Graph integration |
51+
| `openai` | OpenAI integration |
52+
53+
### Key Patterns
54+
55+
**Imports**
56+
- ALL imports MUST be at the top of the file — no imports inside functions, classes, or conditional blocks
57+
- Avoid `TYPE_CHECKING` blocks unless absolutely necessary (genuine circular imports that can't be restructured)
58+
- Avoid dynamic/deferred imports unless absolutely necessary
59+
- Relative imports within the same package, absolute for external packages
60+
61+
**Models**
62+
- Pydantic with `ConfigDict(alias_generator=to_camel)` — snake_case in Python, camelCase in JSON
63+
- `model_dump(by_alias=True)` for serialization, `model_dump(exclude_none=True)` for query params
64+
65+
**Interfaces**
66+
- Protocol classes instead of Abstract Base Classes (ABC)
67+
- Prefer composition over inheritance
68+
69+
**Clients**
70+
- Concrete clients inherit from `BaseClient` (`packages/api/src/microsoft_teams/api/clients/base_client.py`)
71+
- Composition with operation classes for sub-functionality
72+
- async/await for all API calls, return domain models
73+
74+
## Scaffolding (cookiecutter)
75+
76+
```bash
77+
cookiecutter templates/package -o packages # New package
78+
cookiecutter templates/test -o tests # New test package
79+
```
80+
81+
## Dependencies and Build
82+
83+
- UV workspace — packages reference each other via `{ workspace = true }`
84+
- Hatchling build backend
85+
- Dev dependencies in root `pyproject.toml`

examples/a2a-test/src/main.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55

66
import asyncio
7+
import logging
78
import re
89
import uuid
910
from os import getenv
@@ -24,14 +25,20 @@
2425
from microsoft_teams.ai import ChatPrompt, Function, ModelMessage
2526
from microsoft_teams.api import MessageActivity, TypingActivityInput
2627
from microsoft_teams.apps import ActivityContext, App, PluginBase
27-
from microsoft_teams.common import ConsoleLogger, ConsoleLoggerOptions
28+
from microsoft_teams.common import ConsoleFormatter
2829
from microsoft_teams.devtools import DevToolsPlugin
2930
from microsoft_teams.openai.completions_model import OpenAICompletionsAIModel
3031
from pydantic import BaseModel
3132

32-
logger = ConsoleLogger().create_logger("a2a", ConsoleLoggerOptions(level="debug"))
3333
PORT = getenv("PORT", "4000")
3434

35+
# Setup logging
36+
logging.getLogger().setLevel(logging.DEBUG)
37+
stream_handler = logging.StreamHandler()
38+
stream_handler.setFormatter(ConsoleFormatter())
39+
logging.getLogger().addHandler(stream_handler)
40+
logger = logging.getLogger(__name__)
41+
3542

3643
# Setup AI
3744
def get_required_env(key: str) -> str:
@@ -146,7 +153,7 @@ class LocationParams(BaseModel):
146153

147154
# Setup the A2A Server Plugin
148155
plugins: List[PluginBase] = [A2APlugin(A2APluginOptions(agent_card=agent_card)), DevToolsPlugin()]
149-
app = App(logger=logger, plugins=plugins)
156+
app = App(plugins=plugins)
150157

151158

152159
# A2A Server Event Handler

examples/dialogs/src/main.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"""
55

66
import asyncio
7+
import logging
78
import os
8-
from logging import Logger
99
from typing import Any, Optional
1010

1111
from microsoft_teams.api import (
@@ -25,10 +25,15 @@
2525
from microsoft_teams.apps import ActivityContext, App
2626
from microsoft_teams.apps.events.types import ErrorEvent
2727
from microsoft_teams.cards import AdaptiveCard, SubmitAction, SubmitActionData, TaskFetchSubmitActionData, TextBlock
28-
from microsoft_teams.common.logging import ConsoleLogger
28+
from microsoft_teams.common import ConsoleFormatter
29+
30+
# Setup logging
31+
logging.getLogger().setLevel(logging.DEBUG)
32+
stream_handler = logging.StreamHandler()
33+
stream_handler.setFormatter(ConsoleFormatter())
34+
logging.getLogger().addHandler(stream_handler)
35+
logger = logging.getLogger(__name__)
2936

30-
logger_instance = ConsoleLogger()
31-
logger: Logger = logger_instance.create_logger("@apps/dialogs")
3237

3338
if not os.getenv("BOT_ENDPOINT"):
3439
logger.warning("No remote endpoint detected. Using webpages for dialog will not work as expected")

examples/dialogs/src/views/customform/index.html

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<title>Microsoft Teams Task Module Demo</title>
55
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
6-
<script src="https://statics.teams.cdn.office.net/sdk/v1.11.0/js/MicrosoftTeams.min.js"></script>
6+
<script src="https://res.cdn.office.net/teams-js/2.31.1/js/MicrosoftTeams.min.js"></script>
77
<style>
88
body { margin: 0; padding: 10px; }
99
.form-group { margin-bottom: 10px; }
@@ -25,17 +25,21 @@
2525
</div>
2626

2727
<script>
28-
microsoftTeams.initialize();
29-
30-
document.getElementById('customForm').addEventListener('submit', function(event) {
31-
event.preventDefault();
32-
let formData = {
33-
name: document.getElementById('name').value,
34-
email: document.getElementById('email').value,
35-
submissiondialogtype: 'webpage_dialog'
36-
};
37-
microsoftTeams.tasks.submitTask(formData);
38-
});
28+
async function init() {
29+
await microsoftTeams.app.initialize();
30+
31+
document.getElementById('customForm').addEventListener('submit', function(event) {
32+
event.preventDefault();
33+
let formData = {
34+
name: document.getElementById('name').value,
35+
email: document.getElementById('email').value,
36+
submissiondialogtype: 'webpage_dialog'
37+
};
38+
microsoftTeams.dialog.url.submit(formData);
39+
});
40+
}
41+
42+
init();
3943
</script>
4044
</body>
41-
</html>
45+
</html>

examples/graph/src/main.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@
1010
from azure.core.exceptions import ClientAuthenticationError
1111
from microsoft_teams.api import MessageActivity
1212
from microsoft_teams.apps import ActivityContext, App, AppOptions, ErrorEvent, SignInEvent
13+
from microsoft_teams.common import ConsoleFormatter
1314
from microsoft_teams.graph import get_graph_client
1415
from msgraph.generated.users.item.messages.messages_request_builder import ( # type: ignore
1516
MessagesRequestBuilder,
1617
)
1718

19+
# Setup logging
20+
logging.getLogger().setLevel(logging.DEBUG)
21+
stream_handler = logging.StreamHandler()
22+
stream_handler.setFormatter(ConsoleFormatter())
23+
logging.getLogger().addHandler(stream_handler)
1824
logger = logging.getLogger(__name__)
1925

2026
app_options = AppOptions(default_connection_name=os.getenv("CONNECTION_NAME", "graph"))
@@ -39,7 +45,7 @@ async def get_authenticated_graph_client(ctx: ActivityContext[MessageActivity]):
3945
return get_graph_client(ctx.user_token)
4046

4147
except Exception as e:
42-
ctx.logger.error(f"Failed to create Graph client: {e}")
48+
logger.error(f"Failed to create Graph client: {e}")
4349
await ctx.send("🔐 Failed to create authenticated client. Please try signing in again.")
4450
await ctx.sign_in()
4551
return None
@@ -90,12 +96,12 @@ async def handle_profile_command(ctx: ActivityContext[MessageActivity]):
9096
await ctx.send("❌ Could not retrieve your profile information.")
9197

9298
except ClientAuthenticationError as e:
93-
ctx.logger.error(f"Authentication error: {e}")
99+
logger.error(f"Authentication error: {e}")
94100
await ctx.send("🔐 Authentication failed. Please try signing in again.")
95101
await ctx.sign_in()
96102

97103
except Exception as e:
98-
ctx.logger.error(f"Error getting profile: {e}")
104+
logger.error(f"Error getting profile: {e}")
99105
await ctx.send(f"❌ Failed to get your profile: {str(e)}")
100106

101107

@@ -138,11 +144,11 @@ async def handle_emails_command(ctx: ActivityContext[MessageActivity]):
138144
await ctx.send("📪 No recent emails found.")
139145

140146
except ClientAuthenticationError as e:
141-
ctx.logger.error(f"Authentication error: {e}")
147+
logger.error(f"Authentication error: {e}")
142148
await ctx.send("🔐 Authentication failed. You may need additional permissions to read emails.")
143149

144150
except Exception as e:
145-
ctx.logger.error(f"Error getting emails: {e}")
151+
logger.error(f"Error getting emails: {e}")
146152
await ctx.send(f"❌ Failed to get your emails: {str(e)}")
147153

148154

examples/http-adapters/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# HTTP Adapters Examples
2+
3+
Examples showing how to use custom `HttpServerAdapter` implementations and non-managed server patterns with the Teams Python SDK.
4+
5+
## Examples
6+
7+
### 1. Starlette Adapter (`starlette_echo.py`)
8+
9+
A custom `HttpServerAdapter` implementation for [Starlette](https://www.starlette.io/). Demonstrates how to write an adapter for any ASGI framework.
10+
11+
**Pattern**: Custom adapter, SDK-managed server lifecycle (`app.start()`)
12+
13+
```bash
14+
python src/starlette_echo.py
15+
```
16+
17+
### 2. Non-Managed FastAPI (`fastapi_non_managed.py`)
18+
19+
Use your own FastAPI app with your own routes, and let the SDK register `/api/messages` on it. You manage the server lifecycle yourself.
20+
21+
**Pattern**: Default `FastAPIAdapter` with user-provided FastAPI instance, user-managed server (`app.initialize()` + your own `uvicorn.Server`)
22+
23+
```bash
24+
python src/fastapi_non_managed.py
25+
```
26+
27+
## Key Concepts
28+
29+
### Managed vs Non-Managed
30+
31+
| | Managed | Non-Managed |
32+
|---|---|---|
33+
| **Entry point** | `app.start(port)` | `app.initialize()` + start server yourself |
34+
| **Who starts the server** | The SDK (via adapter) | You |
35+
| **When to use** | New apps, simple setup | Existing apps, custom server config |
36+
37+
### Writing a Custom Adapter
38+
39+
Implement the `HttpServerAdapter` protocol:
40+
41+
```python
42+
class MyAdapter:
43+
def register_route(self, method, path, handler): ...
44+
def serve_static(self, path, directory): ...
45+
async def start(self, port): ...
46+
async def stop(self): ...
47+
```
48+
49+
The handler signature is framework-agnostic:
50+
51+
```python
52+
async def handler(request: HttpRequest) -> HttpResponse:
53+
# request = { "body": dict, "headers": dict }
54+
# return { "status": int, "body": object }
55+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[project]
2+
name = "http-adapters"
3+
version = "0.1.0"
4+
description = "Examples showing custom HttpServerAdapter and non-managed server patterns"
5+
readme = "README.md"
6+
requires-python = ">=3.12,<3.15"
7+
dependencies = [
8+
"dotenv>=0.9.9",
9+
"microsoft-teams-apps",
10+
"microsoft-teams-api",
11+
"starlette",
12+
"uvicorn",
13+
"httptools",
14+
]
15+
16+
[tool.uv.sources]
17+
microsoft-teams-apps = { workspace = true }

0 commit comments

Comments
 (0)