SDK-Type Bindings for Azure Functions
SDK-type bindings let Azure Functions expose connector trigger payloads as strongly typed Azure Connectors SDK models instead of raw JSON dictionaries.
Ownership Model
SDK-type deserialization belongs to the
azurefunctions-extensions-connectors
package, not to generated connector modules in this SDK.
The responsibilities are intentionally separated:
| Component | Responsibility |
|---|---|
| Azure Connectors SDK | Generated dataclasses and connector clients that represent the managed connector contract |
| Azure Functions connector extension | Trigger payload normalization, type-specific field conversion, and nested-model deserialization |
| Azure Functions worker | Binding discovery and converter invocation |
Generated connector files are replaced whenever a connector is regenerated.
Do not add from_json() methods or other hand-authored binding logic to those
files, and do not add connector-specific binding templates to the SDK generator.
Why Deserialization Is Extension-Owned
Trigger payloads are runtime binding contracts and can differ from the managed connector Swagger contract:
- A trigger can deliver either a batch or a single item depending on
splitOn. - Callback field names can differ from generated response-model wire names.
- Individual bindings can require conversions, such as mapping Office 365 importance strings to integer SDK values.
- Nested objects such as attachments and sensitivity labels need typed parsing.
- Some bindings return a list, while others return a generated wrapper object.
These behaviors are specific to each Azure Functions SDK type. Keeping them in the extension allows each type to provide its own deserializer without coupling the generic Swagger generator to runtime-specific payload shapes.
Runtime Flow
flowchart LR
A[Connector trigger callback] --> B[ConnectorConverter]
B --> C[SDK type wrapper]
C --> D[Type-specific extension deserializer]
D --> E[Generated Azure Connectors SDK model]
The Office 365 implementation is under
azurefunctions-extensions-connectors/azurefunctions/extensions/connectors/office365/:
- The parent package's
_sdk_type.pycentralizes payload storage, error wrapping, andsupports_deferred_binding()for every connector. - The parent package's
_deserialization.pymaps generated dataclasses and normalizes the common connector callback envelope. - The Office 365
_deserialization.pycontains its aliases, conversions, and type-specific deserializers. - Each public SDK type wrapper selects its deserializer with
_deserialize. connectorConverter.pymaps the function annotation to the wrapper.
Payload Shapes
The extension normalizes both callback shapes.
Batch
{
"body": {
"value": [
{ "id": "item1", "subject": "First item" },
{ "id": "item2", "subject": "Second item" }
]
}
}
Single Item
Adding a Binding Type
1. Keep the SDK Model Generated
Confirm that the required dataclass and its nested types exist in the generated connector module. If the Swagger contract is wrong, fix the SDK generator and regenerate. Do not add runtime deserialization methods to the generated model.
2. Add a Type-Specific Deserializer
Add a function to the connector's extension-owned deserialization module. It
must accept a Datum and return the exact SDK model shape expected by the
binding.
def deserialize_messages(data: Datum) -> list[GeneratedMessage]:
"""Deserialize connector callback messages."""
return [
deserialize_model(GeneratedMessage, item)
for item in parse_payload(data)
]
Use explicit aliases or converters when the callback contract differs from the generated model:
def deserialize_legacy_messages(data: Datum) -> list[LegacyMessage]:
"""Deserialize legacy messages with binding-specific conversions."""
return [
deserialize_model(
LegacyMessage,
item,
aliases={"to": "toRecipients"},
converters={"importance": _deserialize_importance},
)
for item in parse_payload(data)
]
3. Add the SDK Type Wrapper
The wrapper combines the generated model used in function annotations with the shared extension base and selects the deserializer.
class Message(
ConnectorSdkType[list[GeneratedMessage]],
GeneratedMessage,
):
"""Azure Functions binding for connector messages."""
_deserialize = staticmethod(deserialize_messages)
4. Export the Wrapper
Update the extension package only:
- Export the wrapper from the connector package.
- Derive it from the shared
ConnectorSdkType; the converter discovers supported types through this base class without a central registration list. - Add focused extension tests.
The Azure Connectors SDK package exports only generated model types and does not need a binding-specific registration change.
5. Test Behavior
For every deserializer, cover:
- Batch and single-item callback shapes.
- JSON strings and decoded dictionaries when both can reach the converter.
- Field-name aliases between callback JSON and generated model attributes.
- Binding-specific scalar conversions.
- Nested generated models.
- Empty and malformed payload behavior.
- The exact return shape: list, single model, or generated wrapper.
Run the extension tests against the local Azure Connectors SDK source so the
test cannot accidentally use an older installed package that still has a
from_json() method.
Supported Office 365 Bindings
| Extension SDK type | Deserialized SDK model | Return shape |
|---|---|---|
ClientReceiveMessage |
azure.connectors.office365.ClientReceiveMessage |
List of messages |
GraphClientReceiveMessage |
azure.connectors.office365.GraphClientReceiveMessage |
List of messages |
GraphCalendarEventClientReceive |
azure.connectors.office365.GraphCalendarEventClientReceive |
List of events |
GraphCalendarEventListWithActionType |
azure.connectors.office365.GraphCalendarEventListWithActionType |
Event-list wrapper |
Consumer Example
from typing import List
import azure.functions as func
import azurefunctions.extensions.connectors.office365 as office365
app = func.FunctionApp()
@app.connector_trigger(arg_name="messages")
def process_emails(
messages: List[office365.ClientReceiveMessage],
) -> None:
for message in messages:
print(message.subject)
print(message.from_)
print(message.importance)
Application code continues to use the extension's public SDK type annotation. The extension converter returns generated Azure Connectors SDK dataclass instances after applying the binding-specific deserializer.