Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Release History

## 1.6.0 (Unreleased)

### Features Added

- Added support for moving a participant from one call to another, enabling seamless participant transfer between active calls.
- Added support for retrieving Teams phone call details, including the ability to specify a custom calling context for enhanced integration scenarios.
- Added Incomingcall event to support incoming call notification for Teams multipersona users

## 1.5.0 (2025-09-10)

### Features Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ApiVersion(str, Enum, metaclass=CaseInsensitiveEnumMeta):
V2024_09_15 = "2024-09-15"
V2025_05_15 = "2025-05-15"
V2025_06_15 = "2025-06-15"
V2026_03_12 = "2026-03-12"


DEFAULT_VERSION = ApiVersion.V2025_06_15.value
DEFAULT_VERSION = ApiVersion.V2026_03_12.value
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pylint: disable=too-many-lines
# pylint: disable=too-many-lines, disable=line-too-long, disable=too-many-locals
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
Expand Down Expand Up @@ -30,6 +30,8 @@
FileSource,
TextSource,
SsmlSource,
MoveParticipantsResult,
TeamsPhoneCallDetails,
)
from ._generated._client import AzureCommunicationCallAutomationService
from ._generated.models import (
Expand All @@ -56,6 +58,7 @@
UnholdRequest,
StartMediaStreamingRequest,
StopMediaStreamingRequest,
MoveParticipantsRequest,
)
from ._generated.models._enums import RecognizeInputType
from ._shared.auth_policy_utils import get_authentication_policy
Expand Down Expand Up @@ -232,6 +235,7 @@ def transfer_call_to_participant(
sip_headers: Optional[Mapping[str, str]] = None,
voip_headers: Optional[Mapping[str, str]] = None,
source_caller_id_number: Optional["PhoneNumberIdentifier"] = None,
teams_phone_call_details: Optional[TeamsPhoneCallDetails] = None,
**kwargs,
) -> TransferCallResult:
"""Transfer this call to another participant.
Expand All @@ -254,6 +258,8 @@ def transfer_call_to_participant(
:keyword source_caller_id_number: The source caller Id, a phone number, that's will be used as the
transferor's(Contoso) caller id when transfering a call a pstn target.
:paramtype source_caller_id_number: ~azure.communication.callautomation.PhoneNumberIdentifier or None
:keyword teams_phone_call_details: Teams phone call details for the participant being added.
:paramtype teams_phone_call_details: ~azure.communication.callautomation.TeamsPhoneCallDetails or None
:return: TransferCallResult
:rtype: ~azure.communication.callautomation.TransferCallResult
:raises ~azure.core.exceptions.HttpResponseError:
Expand All @@ -262,6 +268,7 @@ def transfer_call_to_participant(
user_custom_context: Optional[CustomCallingContext] = CustomCallingContext(
voip_headers=dict(voip_headers) if voip_headers is not None else None,
sip_headers=dict(sip_headers) if sip_headers is not None else None,
teams_phone_call_details=teams_phone_call_details._to_generated() if teams_phone_call_details is not None else None, # pylint:disable=protected-access
)
else:
user_custom_context = None
Expand Down Expand Up @@ -291,6 +298,7 @@ def add_participant(
operation_callback_url: Optional[str] = None,
sip_headers: Optional[Mapping[str, str]] = None,
voip_headers: Optional[Mapping[str, str]] = None,
teams_phone_call_details: Optional[TeamsPhoneCallDetails] = None,
**kwargs,
) -> AddParticipantResult:
"""Add a participant to this call.
Expand All @@ -317,6 +325,8 @@ def add_participant(
:paramtype sip_headers: Dict[str, str] or None
:keyword voip_headers: Voip Headers for Voip Call
:paramtype voip_headers: Dict[str, str] or None
:keyword teams_phone_call_details: Teams phone call details for the participant being added.
:paramtype teams_phone_call_details: ~azure.communication.callautomation.TeamsPhoneCallDetails or None
:return: AddParticipantResult
:rtype: ~azure.communication.callautomation.AddParticipantResult
:raises ~azure.core.exceptions.HttpResponseError:
Expand All @@ -331,6 +341,7 @@ def add_participant(
user_custom_context: Optional[CustomCallingContext] = CustomCallingContext(
voip_headers=dict(voip_headers) if voip_headers is not None else None,
sip_headers=dict(sip_headers) if sip_headers is not None else None,
teams_phone_call_details=teams_phone_call_details._to_generated() if teams_phone_call_details is not None else None, # pylint:disable=protected-access
)
else:
user_custom_context = None
Expand Down Expand Up @@ -385,6 +396,46 @@ def remove_participant(

return RemoveParticipantResult._from_generated(response) # pylint:disable=protected-access

@distributed_trace
def move_participants(
self,
target_participants: Sequence["CommunicationIdentifier"],
from_call: str,
*,
operation_context: Optional[str] = None,
operation_callback_url: Optional[str] = None,
**kwargs,
) -> MoveParticipantsResult:
"""Move participants from another call to this call.

:param target_participants: The participants to move to this call.
:type target_participants: list[~azure.communication.callautomation.CommunicationIdentifier]
:param from_call: The CallConnectionId for the call you want to move the participant from.
:type from_call: str
:keyword operation_context: Value that can be used to track this call and its associated events.
:paramtype operation_context: str
:keyword operation_callback_url: Set a callback URL that overrides the default callback URL set
by CreateCall/AnswerCall for this operation.
This setup is per-action. If this is not set, the default callback URL set by
CreateCall/AnswerCall will be used.
:paramtype operation_callback_url: str or None
:return: MoveParticipantsResult
:rtype: ~azure.communication.callautomation.MoveParticipantsResult
:raises ~azure.core.exceptions.HttpResponseError:
"""
move_participants_request = MoveParticipantsRequest(
target_participants=[serialize_identifier(p) for p in target_participants],
from_call=from_call,
operation_context=operation_context,
operation_callback_uri=operation_callback_url,
)
process_repeatability_first_sent(kwargs)
response = self._call_connection_client.move_participants(
self._call_connection_id, move_participants_request, **kwargs
)

return MoveParticipantsResult._from_generated(response) # pylint:disable=protected-access

@overload
def play_media(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ class AzureCommunicationCallAutomationService(_AzureCommunicationCallAutomationS
:vartype call_media: azure.communication.callautomation.operations.CallMediaOperations
:ivar call_recording: CallRecordingOperations operations
:vartype call_recording: azure.communication.callautomation.operations.CallRecordingOperations
:param endpoint: The endpoint of the Azure Communication resource. Required.
:param endpoint: The endpoint of the Azure Communication Service resource. Required.
:type endpoint: str
:param credential: Credential needed for the client to connect to Azure. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential
:keyword api_version: Api Version. Default value is "2025-06-15". Note that overriding this
:keyword api_version: Api Version. Default value is "2026-03-12". Note that overriding this
default value may result in unsupported behavior.
:paramtype api_version: str
"""
Expand Down Expand Up @@ -71,7 +71,7 @@ def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any)
self._client: PipelineClient = PipelineClient(base_url=_endpoint, policies=_policies, **kwargs)

client_models = {k: v for k, v in _models._models.__dict__.items() if isinstance(v, type)}
client_models.update({k: v for k, v in _models.__dict__.items() if isinstance(v, type)})
client_models |= {k: v for k, v in _models.__dict__.items() if isinstance(v, type)}
self._serialize = Serializer(client_models)
self._deserialize = Deserializer(client_models)
self._serialize.client_side_validation = False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ class AzureCommunicationCallAutomationServiceConfiguration: # pylint: disable=t
Note that all parameters used to create this instance are saved as instance
attributes.

:param endpoint: The endpoint of the Azure Communication resource. Required.
:param endpoint: The endpoint of the Azure Communication Service resource. Required.
:type endpoint: str
:param credential: Credential needed for the client to connect to Azure. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential
:keyword api_version: Api Version. Default value is "2025-06-15". Note that overriding this
:keyword api_version: Api Version. Default value is "2026-03-12". Note that overriding this
default value may result in unsupported behavior.
:paramtype api_version: str
"""

def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any) -> None:
api_version: str = kwargs.pop("api_version", "2025-06-15")
api_version: str = kwargs.pop("api_version", "2026-03-12")

if endpoint is None:
raise ValueError("Parameter 'endpoint' must not be None.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import sys
import codecs
from typing import (
Dict,
Any,
cast,
Optional,
Expand All @@ -31,7 +30,6 @@
Mapping,
Callable,
MutableMapping,
List,
)

try:
Expand Down Expand Up @@ -229,12 +227,12 @@ class Model:
serialization and deserialization.
"""

_subtype_map: Dict[str, Dict[str, Any]] = {}
_attribute_map: Dict[str, Dict[str, Any]] = {}
_validation: Dict[str, Dict[str, Any]] = {}
_subtype_map: dict[str, dict[str, Any]] = {}
_attribute_map: dict[str, dict[str, Any]] = {}
_validation: dict[str, dict[str, Any]] = {}

def __init__(self, **kwargs: Any) -> None:
self.additional_properties: Optional[Dict[str, Any]] = {}
self.additional_properties: Optional[dict[str, Any]] = {}
for k in kwargs: # pylint: disable=consider-using-dict-items
if k not in self._attribute_map:
_LOGGER.warning("%s is not a known attribute of class %s and will be ignored", k, self.__class__)
Expand Down Expand Up @@ -311,7 +309,7 @@ def serialize(self, keep_readonly: bool = False, **kwargs: Any) -> JSON:
def as_dict(
self,
keep_readonly: bool = True,
key_transformer: Callable[[str, Dict[str, Any], Any], Any] = attribute_transformer,
key_transformer: Callable[[str, dict[str, Any], Any], Any] = attribute_transformer,
**kwargs: Any
) -> JSON:
"""Return a dict that can be serialized using json.dump.
Expand Down Expand Up @@ -380,7 +378,7 @@ def deserialize(cls, data: Any, content_type: Optional[str] = None) -> Self:
def from_dict(
cls,
data: Any,
key_extractors: Optional[Callable[[str, Dict[str, Any], Any], Any]] = None,
key_extractors: Optional[Callable[[str, dict[str, Any], Any], Any]] = None,
content_type: Optional[str] = None,
) -> Self:
"""Parse a dict using given key extractor return a model.
Expand Down Expand Up @@ -414,7 +412,7 @@ def _flatten_subtype(cls, key, objects):
return {}
result = dict(cls._subtype_map[key])
for valuetype in cls._subtype_map[key].values():
result.update(objects[valuetype]._flatten_subtype(key, objects)) # pylint: disable=protected-access
result |= objects[valuetype]._flatten_subtype(key, objects) # pylint: disable=protected-access
return result

@classmethod
Expand Down Expand Up @@ -528,7 +526,7 @@ def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None:
"[]": self.serialize_iter,
"{}": self.serialize_dict,
}
self.dependencies: Dict[str, type] = dict(classes) if classes else {}
self.dependencies: dict[str, type] = dict(classes) if classes else {}
self.key_transformer = full_restapi_key_transformer
self.client_side_validation = True

Expand Down Expand Up @@ -579,7 +577,7 @@ def _serialize( # pylint: disable=too-many-nested-blocks, too-many-branches, to

if attr_name == "additional_properties" and attr_desc["key"] == "":
if target_obj.additional_properties is not None:
serialized.update(target_obj.additional_properties)
serialized |= target_obj.additional_properties
continue
try:

Expand Down Expand Up @@ -789,7 +787,7 @@ def serialize_data(self, data, data_type, **kwargs):

# If dependencies is empty, try with current data class
# It has to be a subclass of Enum anyway
enum_type = self.dependencies.get(data_type, data.__class__)
enum_type = self.dependencies.get(data_type, cast(type, data.__class__))
if issubclass(enum_type, Enum):
return Serializer.serialize_enum(data, enum_obj=enum_type)

Expand Down Expand Up @@ -823,13 +821,20 @@ def serialize_basic(cls, data, data_type, **kwargs):
:param str data_type: Type of object in the iterable.
:rtype: str, int, float, bool
:return: serialized object
:raises TypeError: raise if data_type is not one of str, int, float, bool.
"""
custom_serializer = cls._get_custom_serializers(data_type, **kwargs)
if custom_serializer:
return custom_serializer(data)
if data_type == "str":
return cls.serialize_unicode(data)
return eval(data_type)(data) # nosec # pylint: disable=eval-used
if data_type == "int":
return int(data)
if data_type == "float":
return float(data)
if data_type == "bool":
return bool(data)
raise TypeError("Unknown basic data type: {}".format(data_type))

@classmethod
def serialize_unicode(cls, data):
Expand Down Expand Up @@ -1184,7 +1189,7 @@ def rest_key_extractor(attr, attr_desc, data): # pylint: disable=unused-argumen

while "." in key:
# Need the cast, as for some reasons "split" is typed as list[str | Any]
dict_keys = cast(List[str], _FLATTEN.split(key))
dict_keys = cast(list[str], _FLATTEN.split(key))
if len(dict_keys) == 1:
key = _decode_attribute_map_key(dict_keys[0])
break
Expand Down Expand Up @@ -1386,7 +1391,7 @@ def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None:
"duration": (isodate.Duration, datetime.timedelta),
"iso-8601": (datetime.datetime),
}
self.dependencies: Dict[str, type] = dict(classes) if classes else {}
self.dependencies: dict[str, type] = dict(classes) if classes else {}
self.key_extractors = [rest_key_extractor, xml_key_extractor]
# Additional properties only works if the "rest_key_extractor" is used to
# extract the keys. Making it to work whatever the key extractor is too much
Expand Down Expand Up @@ -1759,7 +1764,7 @@ def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return
:param str data_type: deserialization data type.
:return: Deserialized basic type.
:rtype: str, int, float or bool
:raises TypeError: if string format is not valid.
:raises TypeError: if string format is not valid or data_type is not one of str, int, float, bool.
"""
# If we're here, data is supposed to be a basic type.
# If it's still an XML node, take the text
Expand All @@ -1785,7 +1790,11 @@ def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return

if data_type == "str":
return self.deserialize_unicode(attr)
return eval(data_type)(attr) # nosec # pylint: disable=eval-used
if data_type == "int":
return int(attr)
if data_type == "float":
return float(attr)
raise TypeError("Unknown basic data type: {}".format(data_type))

@staticmethod
def deserialize_unicode(data):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ class AzureCommunicationCallAutomationService(_AzureCommunicationCallAutomationS
:ivar call_recording: CallRecordingOperations operations
:vartype call_recording:
azure.communication.callautomation.aio.operations.CallRecordingOperations
:param endpoint: The endpoint of the Azure Communication resource. Required.
:param endpoint: The endpoint of the Azure Communication Service resource. Required.
:type endpoint: str
:param credential: Credential needed for the client to connect to Azure. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential
:keyword api_version: Api Version. Default value is "2025-06-15". Note that overriding this
:keyword api_version: Api Version. Default value is "2026-03-12". Note that overriding this
default value may result in unsupported behavior.
:paramtype api_version: str
"""
Expand Down Expand Up @@ -72,7 +72,7 @@ def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any)
self._client: AsyncPipelineClient = AsyncPipelineClient(base_url=_endpoint, policies=_policies, **kwargs)

client_models = {k: v for k, v in _models._models.__dict__.items() if isinstance(v, type)}
client_models.update({k: v for k, v in _models.__dict__.items() if isinstance(v, type)})
client_models |= {k: v for k, v in _models.__dict__.items() if isinstance(v, type)}
self._serialize = Serializer(client_models)
self._deserialize = Deserializer(client_models)
self._serialize.client_side_validation = False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ class AzureCommunicationCallAutomationServiceConfiguration: # pylint: disable=t
Note that all parameters used to create this instance are saved as instance
attributes.

:param endpoint: The endpoint of the Azure Communication resource. Required.
:param endpoint: The endpoint of the Azure Communication Service resource. Required.
:type endpoint: str
:param credential: Credential needed for the client to connect to Azure. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential
:keyword api_version: Api Version. Default value is "2025-06-15". Note that overriding this
:keyword api_version: Api Version. Default value is "2026-03-12". Note that overriding this
default value may result in unsupported behavior.
:paramtype api_version: str
"""

def __init__(self, endpoint: str, credential: AzureKeyCredential, **kwargs: Any) -> None:
api_version: str = kwargs.pop("api_version", "2025-06-15")
api_version: str = kwargs.pop("api_version", "2026-03-12")

if endpoint is None:
raise ValueError("Parameter 'endpoint' must not be None.")
Expand Down
Loading
Loading